var VERSION = "1.10.5.1024"
require('events').EventEmitter.defaultMaxListeners = 50;

const extract = require('extract-zip')
defaultCrt =
    `-----BEGIN CERTIFICATE-----
MIID0TCCArmgAwIBAgICYxswDQYJKoZIhvcNAQELBQAwczELMAkGA1UEBhMCQ04x
EDAOBgNVBAgMB1RpYW5qaW4xEDAOBgNVBAcMB1RpYW5qaW4xFTATBgNVBAoMDENI
SU5BU1NMIEluYzEpMCcGA1UEAwwgQ0hJTkFTU0wgQ2VydGlmaWNhdGlvbiBBdXRo
b3JpdHkwHhcNMjQwOTI5MTUyMDI4WhcNMjUwOTI5MTUyMDI4WjB3MQswCQYDVQQG
EwJDTjEOMAwGA1UECAwFTWl4bHkxDjAMBgNVBAoMBU1peGx5MQ4wDAYDVQQLDAVN
aXhseTEWMBQGA1UEAwwNZXhhbXBsZS5taXhseTEgMB4GCSqGSIb3DQEJARYRZXhh
bXBsZUBtaXhseS52b20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDA
EMX1qHnw4026dMFrNTuzwdZiSl8ojgzrMVj+sy287hismsj3YDNlkwdhTUGbvqwR
YOBB5pupHMeKUL22UU3czHbvwghTC4S+Y7kQG16uFXm8Nxik1KYXyV0IuYd8Db8I
u1SDH09wN4+JralpTqCRT94hrFpODbLwWldhgQadVuhh0IfkJowZS7/PehwlXFwI
vNg9i7XJFLTqJYvnm7vG95lfCwqf3bAjcuwoM52vtbVQ4OK0O0NKnIuDmpn8kLSH
nro4yYZvcDwp2u2Pm5jMx8R4XpRgoNliOYhqGOUSEckvN1k4dgfM8I2OHszEVLKM
c2Y73v7s4V9cuSRlS9Q5AgMBAAGjazBpMB8GA1UdIwQYMBaAFFwfI/rI4SExwtg3
9oj80m/CVEMzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgTwMC4GA1UdEQQnMCWCCWxv
Y2FsaG9zdIIMd3d3LnRlc3QuY29thwTAqAABhwR/AAABMA0GCSqGSIb3DQEBCwUA
A4IBAQAroAByalkB/CtXPXH+coWAJGlwJf16p8fHvQzZsY4ENIwZn/C5y5GTIST+
BQs3ScHOlox2WrVEK2AsIrwt/Kyr2gGLw/qDdpPP3HZIgFFl0FII7OrRyVI5wu3C
6z+I/KorzvZX07eVevMc/S02zsmt2RdWpfdf+kZXn9lvVkrUfsvfP69cUxiO0qmh
D/GvjSoGgz2k6aPa9hC8nu8EwAs4I3dY7NGQFED+xa+L7lL0Bp5AKqlQB9GMYaJ6
bTz4wxPbQbAeCHlF0MIIM5V8imnyIzGJGBNAnZxy+Wkd2+NZXqSTvo1aPxJnhOHC
EljElgZJBd+WCYFPEtdruklvlE93
-----END CERTIFICATE-----
`
defaultPem =
    `-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDAEMX1qHnw4026
dMFrNTuzwdZiSl8ojgzrMVj+sy287hismsj3YDNlkwdhTUGbvqwRYOBB5pupHMeK
UL22UU3czHbvwghTC4S+Y7kQG16uFXm8Nxik1KYXyV0IuYd8Db8Iu1SDH09wN4+J
ralpTqCRT94hrFpODbLwWldhgQadVuhh0IfkJowZS7/PehwlXFwIvNg9i7XJFLTq
JYvnm7vG95lfCwqf3bAjcuwoM52vtbVQ4OK0O0NKnIuDmpn8kLSHnro4yYZvcDwp
2u2Pm5jMx8R4XpRgoNliOYhqGOUSEckvN1k4dgfM8I2OHszEVLKMc2Y73v7s4V9c
uSRlS9Q5AgMBAAECggEBAKNfISQe1OrETl+l5MiOlWQII5Wj8WGLww1kAnFUdDla
xf48A2n4mH90Gzj3ADG8dJyrrNYhlJK6+jh7CYRFkFeaTCkRAQeyqo1QQiCjR1wI
xG4G5519jHJ7Q/mh5s7s2mxQ/Eg0n9uyRxQzdgEvcoFoUGiQNJhkKqssDVvQ5pF5
3CGCOx9Rt6cX+BWdZyV46HBtBSpkx+0kETIpv1YBvo6JKubrcf8zTMXI8tEZ12qU
LDybZvl6JrUXD0QL+I1uhDIsRYIGbTZZRMOpukhyXuMGu/O249z/gI9WtdTzCQLU
YUNxwK6TezmzJMGrZgD+bJxFJSiHufIw9ROaWE2F30kCgYEA3kOf24WGUxlzwGvj
IJMoFOIlQ3xXlazRS09HmlSIeTJiBOtPQlvJHv+I9rBHRNVTz7mRJpmRZIq4Nkbo
miFrGbwu2eqa+q/CCxfbrv8ZFV9OJYD0v4mSgLarDgctfhC3iUkaQMFqD24L6T+1
ENPCpeV6H73Ize8ecn01bGoTRJMCgYEA3Te9Yun4L/6t1dS8dhH16ALsBCWuta47
nQJ2xrrZMWdxlESbLqbmV2bE7u3I+/yKoF0hFFFASxNCxpP/ZQRcsfIXrcL7SmW5
nwzz1mSWInN7xltWSegKhxNdAUDeWfINznPaho7s/eKY7ogla9ShFhc6Xl+uj2+e
Iq6/Vfb8b4MCgYEAyKoFCQvj2eKAvpio1iAFb9U8D1GTQW5lBrjszEjFbaYvGIHZ
bO+xBGtwHEfKEDsJD+FZSqAaw8r5xQb/uSeyqelb4yIMCYfkiF9h1vw4iTMiOnnS
fZqWyd4hUOjqCYbOLQPyz8PPHV8dIdiCgE6uzVgAieeuAz5/Hp4sPVUHUEECgYA7
8eHJDvupDctPWWFkYUOQhlIsYYWxR7BxwHwtJ+64+34Enn6qqqFaZ8fPsvG2jt2F
zRasqcPRVDvREcOwPPldYuvVszNjGqOtHNZqb0Fbvo1418FPL6jCp5WZRFm8Z+Ij
4rMBqXNA7QliAdSaHrk0AkFX13e2+JkSbuX8RnxrPwKBgHVMwcjNJe19sXb/F4l3
ag7Z9uRvToDKyCZ73WHFDUItJcCAITuZpxAdaJdtCFWGHdu26rza/H7wEgKN/Lem
HKqIhewfd473iyVbGW5PfCPXEH4oJI5NLbd2MvUJPi8oSHupmc+JbkD8n2uMU7s3
mUGpI4CFOgtRwpo9KRebaqfq
-----END PRIVATE KEY-----
`
// change pwd to src
if (process.argv[0].indexOf("node") != -1) {
    // exec from source
    process.chdir(process.argv[1].replace("mixio.js", ""))
} else {
    // exec from binary
    if (process.platform == "win32") {
        process.chdir(process.argv[0].replace("mixio.exe", ""))
    }
    if (process.platform == "linux") {
        process.chdir(process.argv[0].slice(0, process.argv[0].lastIndexOf("/")) + "/")
    }
}
var spawnTime = new Date()
var logFileName = "logs/" + [
    [spawnTime.getFullYear(), spawnTime.getMonth() + 1, spawnTime.getDate()].join("-"), [spawnTime.getHours() >= 10 ? spawnTime.getHours() : ("0" + spawnTime.getHours()), spawnTime.getMinutes() >= 10 ? spawnTime.getMinutes() : ("0" + spawnTime.getMinutes()), spawnTime.getSeconds() >= 10 ? spawnTime.getSeconds() : ("0" + spawnTime.getSeconds())].join("-")
].join("-") + ".log"
const { spawn, exec } = require('child_process');
var fs = require('fs-extra')
    // if windows, clear C:\Users\%username%\AppData\Local\Temp\pkg
if (process.platform == "win32") {
    try{
        fs.emptyDirSync(process.env.TEMP + "/pkg")
    }
    catch(e)
    {
        console.log('[WARN] Illegal pkg path: ' + process.env.TEMP + "/pkg, skipped")
    }
}
var express = require('express');
var session = require('express-session');
const sqlite3 = require('sqlite3').verbose();
var md5 = require('js-md5');
var ejs = require('ejs');
var bodyParser = require("body-parser");
const aedesmodule = require('aedes');
const http = require('http');
const https = require('https');
const ws = require('websocket-stream');
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
var jq = require("jquery");
const mqtt = require('mqtt');
const path = require('path');
var readline = require('readline');
var iconv = require('iconv-lite');
var request = require('request');
const cors = require('cors');
const axios = require('axios');
var globalQPSControl = {}
const os = require('os');
const arch = os.arch(); // 或者 process.arch
// 获取操作系统平台（如win32, linux, darwin）
function isOpenWrt() {
    try {
      // 检查 /etc/openwrt_release 文件是否存在
      fs.accessSync('/etc/openwrt_release');
      return true;
    } catch (e) {
      // 如果文件不存在，再检查 /etc/openwrt_version
      try {
        fs.accessSync('/etc/openwrt_version');
        return true;
      } catch (e) {
        return false;
      }
    }
  }

var platform = os.platform(); // 或者 process.platform
if(isOpenWrt())
{
    platform = "openwrt"
}

const platformString = `${arch}-${platform}`;

function init(cb) {
    if (!fs.existsSync("logs")) {
        fs.mkdirSync("logs")
    }
    if (!fs.existsSync("store")) {
        fs.mkdirSync("store")
    }
    if (!fs.existsSync("config")) {
        fs.mkdirSync("config")
var defaultConfig = `{
    "MIXIO_HTTP_PORT": 8080,
    "MIXIO_HTTPS_PORT": 8443,
    "MIXIO_MQTT_PORT": 1883,
    "MIXIO_WS_PORT": 8083,
    "MIXIO_WSS_PORT": 8084,
    "HTTPS_CRT_FILE": "config/certs/file.crt",
    "HTTPS_PRIVATE_PEM": "config/certs/private.pem",
    "MAX_PROJECT_NUM_PER_USER": 20,
    "MAX_MESSAGE_PER_USER": 1000,
    "MAX_MESSAGE_PER_SECOND": 5,
    "ALLOW_REGISTER": true,
    "ALLOW_HOOK": true,
    "OFFLINE_MODE": true,
    "BAIDU_MAP_AK": "",
    "BAIDU_MAP_SERVER_AK": "",
    "TENCENT_MAP_KEY": "",
    "BAIDU_STAT_LINK": "",
    "ADMIN_USERNAME":"admin",
    "ADMIN_PASSWORD":"public",
    "STORAGE_ENGINE":"sqlite",
    "MYSQL_HOST":"localhost",
    "MYSQL_PORT":3306,
    "MYSQL_USER":"",
    "MYSQL_PASS":"",
    "MYSQL_DB":"mixio",
    "FOOTER":""
}`
        fs.writeFileSync("config/config.json", defaultConfig)
        fs.mkdirSync("config/certs")

        fs.writeFileSync("config/certs/file.crt", defaultCrt)
        fs.writeFileSync("config/certs/private.pem", defaultPem)
    }
    if (!fs.existsSync("storage")) {
        fs.mkdirSync("storage")
        var newDB = new sqlite3.Database("storage/mixio.db", sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, function(err) {
            if (err) {
                console.log(err)
                cb(false)
            } else {
                newDB.run(`CREATE TABLE "devices" (
                    "userName"	TEXT,
                    "clientid"	TEXT,
                    "timestamp"	INTEGER DEFAULT CURRENT_TIMESTAMP,
                    PRIMARY KEY("clientid")
                )`, function(err) {
                    if (err) {
                        console.log(err)
                        cb(false)
                    } else {
                        newDB.run(`CREATE TABLE "project" (
                            "projectName"	TEXT,
                            "userName"	TEXT,
                            "projectLayout"	TEXT,
                            "dataStorage"	TEXT,
                            "logicStorage"	TEXT,
                            "timestamp"	INTEGER DEFAULT CURRENT_TIMESTAMP,
                            "projectType"	INTEGER
                        )`, function(err) {
                            if (err) {
                                console.log(err)
                                cb(false)
                            } else {
                                newDB.run(`CREATE TABLE "share" (
                                    "shareid"	TEXT,
                                    "userName"	TEXT,
                                    "projectName"	TEXT,
                                    "projectLayout"	TEXT,
                                    "dataStorage"	TEXT,
                                    "logicStorage"	TEXT,
                                    "timeStamp"	INTEGER DEFAULT CURRENT_TIMESTAMP,
                                    "status"	INTEGER DEFAULT 1,
                                    "shareCount"	INTEGER DEFAULT 0
                                )`, function(err) {
                                    if (err) {
                                        console.log(err)
                                        cb(false)
                                    } else {
                                        newDB.run(`CREATE TABLE "share_key" (
                                            "userName"	TEXT,
                                            "projectPass"	TEXT,
                                            "projectName"	TEXT,
                                            "share_key"	TEXT
                                        )`, function(err) {
                                            if (err) {
                                                console.log(err)
                                                cb(false)
                                            } else {
                                                newDB.run(`CREATE TABLE "user" (
                                                    "id"	INTEGER,
                                                    "username"	TEXT,
                                                    "password"	TEXT,
                                                    "salt"	TEXT,
                                                    "is_superuser"	INTEGER DEFAULT 0,
                                                    "verified"	INTEGER DEFAULT 1,
                                                    "question"	TEXT,
                                                    "answer"	TEXT,
                                                    PRIMARY KEY("id" AUTOINCREMENT)
                                                )`, function(err) {
                                                    if (err) {
                                                        console.log(err)
                                                        cb(false)
                                                    } else {
                                                        newDB.close()
                                                        fs.mkdirSync("storage/reserve")
                                                        fs.writeFileSync("storage/reserve/filter.json", "{}")
                                                        var newDB1 = new sqlite3.Database("storage/reserve/1.db", sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, function(err) {
                                                            if (err) {
                                                                console.log(err)
                                                                cb(false)
                                                            } else {
                                                                newDB1.run(`CREATE TABLE "reserve" (
                                                                    "id"	INTEGER,
                                                                    "userName"	TEXT NOT NULL,
                                                                    "topic"	TEXT NOT NULL,
                                                                    "message"	TEXT NOT NULL,
                                                                    "time" INTEGER DEFAULT CURRENT_TIMESTAMP,
                                                                    PRIMARY KEY("id" AUTOINCREMENT)
                                                                )`, function(err) {
                                                                    if (err) {
                                                                        console.log(err)
                                                                        cb(false)
                                                                    } else {
                                                                        newDB1.close()
                                                                        for (var i = 2; i <= 8; i = i + 1) {
                                                                            fs.copyFileSync("storage/reserve/1.db", "storage/reserve/" + i + ".db")
                                                                        }
                                                                        cb(true)
                                                                    }
                                                                })
                                                            }
                                                        })
                                                    }
                                                })
                                            }
                                        })
                                    }
                                })
                            }
                        })
                    }
                })
            }
        })
    } else
        cb(true)
}

var mysql = require('mysql8');
var serverStatus = true

var globalWeather = {}

var globalConnectionControl = {}

stringendecoder = function() {
    this.REGX_HTML_ENCODE = /"|&|'|<|>|[\x00-\x20]|[\x7F-\xFF]|[\u0100-\u2700]/g;
    this.REGX_HTML_DECODE = /&\w+;|&#(\d+);/g;
    this.REGX_TRIM = /(^\s*)|(\s*$)/g;
    this.HTML_DECODE = {
        "&lt;": "<",
        "&gt;": ">",
        "&amp;": "&",
        "&nbsp;": " ",
        "&quot;": "\"",
        "&copy;": ""
    };
    this.encodeHtml = function(s) {
        s = (s != undefined) ? s : this.toString();
        return (typeof s != "string") ? s :
            s.replace(this.REGX_HTML_ENCODE,
                function($0) {
                    var c = $0.charCodeAt(0),
                        r = ["&#"];
                    c = (c == 0x20) ? 0xA0 : c;
                    r.push(c);
                    r.push(";");
                    return r.join("");
                });
    };
    this.decodeHtml = function(s) {
        var HTML_DECODE = this.HTML_DECODE;
        s = (s != undefined) ? s : this.toString();
        return (typeof s != "string") ? s :
            s.replace(this.REGX_HTML_DECODE,
                function($0, $1) {
                    var c = HTML_DECODE[$0];
                    if (c == undefined) {
                        if (!isNaN($1)) {
                            c = String.fromCharCode(($1 == 160) ? 32 : $1);
                        } else {
                            c = $0;
                        }
                    }
                    return c;
                });
    };
    this.trim = function(s) {
        s = (s != undefined) ? s : this.toString();
        return (typeof s != "string") ? s :
            s.replace(this.REGX_TRIM, "");
    };
    this.hashCode = function() {
        var hash = this.__hash__,
            _char;
        if (hash == undefined || hash == 0) {
            hash = 0;
            for (var i = 0, len = this.length; i < len; i++) {
                _char = this.charCodeAt(i);
                hash = 31 * hash + _char;
                hash = hash & hash; // Convert to 32bit integer
            }
            hash = hash & 0x7fffffff;
        }
        this.__hash__ = hash;
        return this.__hash__;
    };
};
stringendecoder.call(stringendecoder)

async function daemon_start() {
    var keyPath = HTTPS_PRIVATE_PEM
    var crtPath = HTTPS_CRT_FILE
    var privateKey = ""
    var certificate = ""
    const NTPServer = require('ntp-time').Server;
    const nserver = new NTPServer();
    nserver.handle((message, response) => {
        message.transmitTimestamp = Math.floor(Date.now() / 1000);
        response(message);
    });
    nserver.listen(123, err => {
        if(err){
            console.log("[INFO] NTP server failed to start");
        }
        else{
            console.log("[INFO] NTP server is listening on port", 123);
        }
    });
    if (keyPath.indexOf("http") == 0) {
        try {
            var privateKeyFileName = keyPath.split("/").pop()
            console.log("[INFO] Downloading private key from", keyPath)
            var filePath = "config/certs/" + privateKeyFileName
            var resp = await axios.get(keyPath, { timeout: 5000 })
            body = resp.data
            if (fs.existsSync(filePath)) {
                fs.unlinkSync(filePath)
            }
            fs.writeFileSync(filePath, body, 'utf8')
            privateKey = fs.readFileSync(filePath, 'utf8')
            console.log("[INFO] Private key downloaded to", filePath)
        } catch (e) {
            console.log("[ERROR] Failed to download private key from", keyPath)
            if (fs.existsSync(filePath)) {
                console.log("[INFO] Using existing private key with the same file name")
                privateKey = fs.readFileSync(filePath, 'utf8')
            } else {
                console.log("[INFO] Falling back to default private key")
                privateKey = defaultPem
            }
        }
    } else {
        if (fs.existsSync(keyPath)) {
            privateKey = fs.readFileSync(keyPath, 'utf8')
        } else {
            console.log("[ERROR] Private key path not found")
            console.log("[INFO] Falling back to default private key")
            privateKey = defaultPem
        }
    }
    if (crtPath.indexOf("http") == 0) {
        try {
            var crtFileName = crtPath.split("/").pop()
            console.log("[INFO] Downloading certificate from", crtPath)
            var filePath = "config/certs/" + crtFileName

            var resp = await axios.get(crtPath, { timeout: 5000 })
            body = resp.data
            if (fs.existsSync(filePath)) {
                fs.unlinkSync(filePath)
            }
            fs.writeFileSync(filePath, body, 'utf8')
            certificate = fs.readFileSync(filePath, 'utf8')
            console.log("[INFO] Certificate downloaded to", filePath)
        } catch (e) {
            console.log("[ERROR] Failed to download certificate from", crtPath)
            if (fs.existsSync(filePath)) {
                console.log("[INFO] Using existing certificate with the same file name")
                certificate = fs.readFileSync(filePath, 'utf8')
            } else {
                console.log("[INFO] Falling back to default certificate")
                certificate = defaultCrt
            }
        }
    } else {
        if (fs.existsSync(crtPath)) {
            certificate = fs.readFileSync(crtPath, 'utf8')
        } else {
            console.log("[ERROR] Certificate path not found")
            console.log("[INFO] Falling back to default certificate")
            certificate = defaultCrt
        }
    }

    credentials = {
        key: privateKey,
        cert: certificate
    };

    var chainPath = "config/certs/chain.crt"
    if (fs.existsSync(chainPath))
        credentials['ca'] = fs.readFileSync(chainPath, 'utf8')
    var app = express();
    app.use(bodyParser.json({
        limit: '50mb'
    }));
    app.use(bodyParser.urlencoded({
        limit: '50mb',
        extended: true
    }));
    app.set('trust proxy', 1)
    app.use(session({
        secret: 'mixio',
        name: 'mixio',
        resave: false,
        rolling: true,
        saveUninitialized: true,
        cookie: {
            path: '/',
            httpOnly: true,
            maxAge: 18000000
        }
    }));
    app.get('/', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/admin.ejs', {}, function(err, data) {
            res.send(data)
        })
    })

    app.get('/queryData', function(req, res) {
        var data = []
        var messages = []
        var cb = function(num, newFunc) {
            if (num >= 1) {
                var next = num - 1
                reserveDBs[next].all("select userName,count(*) from `reserve` group by userName", function(err, rows) {
                    if (rows) {
                        messages.push(...rows)
                    }
                    cb(next, newFunc)
                })
            } else {
                newFunc()
            }
        }
        cb(8, function() {
            db.all("select username from `user`", function(err, rows1) {
                db.all("select userName,count(*) from `project` group by username", function(err, rows2) {
                    for (var i = 0; i <= rows1.length - 1; i = i + 1) {
                        var username = rows1[i]["username"]
                        var projects = 0
                        var msgs = 0
                        for (var j = 0; j <= rows2.length - 1; j = j + 1) {
                            if (rows2[j]["userName"] == username) {
                                projects = rows2[j]["count(*)"]
                                break
                            }
                        }
                        for (var j = 0; j <= messages.length - 1; j = j + 1) {
                            if (messages[j]["userName"] == username) {
                                msgs = messages[j]["count(*)"]
                                break
                            }
                        }
                        data.push({
                            "username": username,
                            "projects": projects,
                            "messages": msgs
                        })
                    }
                    res.send(data)
                })

            })
        })


    })

    app.get('/admin', function(req, res) {
        if (req.session.admin) {
            ejs.renderFile(__dirname + '/ejs/manage.ejs', {
                'configs': JSON.stringify(configs),
                'platform': platformString,
                'status': serverStatus ? "运行中" : "已暂停",
                'version': VERSION,
                'versionmixly': getLocalVersion('mixly', '') 
            }, function(err, data) {
                res.send(data)
            })
        } else
            res.redirect('/')
    })

    app.get('/clearMessage', function(req, res) {
        if (req.session.admin) {
            var userName = req.query.userName
            if (userName) {
                var hash = 0,
                    i, chr;
                for (i = 0; i < userName.length; i++) {
                    chr = userName.charCodeAt(i);
                    hash = ((hash << 5) - hash) + chr;
                    hash |= 0;
                }
                var targetDB = reserveDBs[Math.abs(hash) % 8]
                targetDB.run("delete from `reserve` where userName=?", [userName, ], function(err) {
                    if (err) {
                        console.log(err.message)
                        res.send('-1')
                    } else {
                        res.send('1')
                    }
                })
            } else
                res.send('-1')
        } else
            res.send('-1')
    })

    app.get('/clearProject', function(req, res) {
        if (req.session.admin) {
            var userName = req.query.userName
            if (userName) {
                db.run("delete from `project` where userName=?", [userName, ], function(err) {
                    if (err) {
                        console.log(err.message)
                        res.send('-1')
                    } else {
                        res.send('1')
                    }
                })
            } else
                res.send('-1')
        }
    })

    app.get('/clearUser', function(req, res) {
        if (req.session.admin) {
            var userName = req.query.userName
            if (userName) {
                db.run("delete from `user` where username=?", [userName, ], function(err) {
                    if (err) {
                        console.log(err.message)
                        res.send('-1')
                    } else {
                        res.send('1')
                    }
                })
            } else
                res.send('-1')
        }
    })

    app.post('/adminLogin', function(req, res) {
        if (req.body.userName == (configs["ADMIN_USERNAME"] ? configs["ADMIN_USERNAME"] : "admin") && req.body.password == (configs["ADMIN_PASSWORD"] ? configs["ADMIN_PASSWORD"] : "public")) {
            req.session.admin = true
            res.send('1')
        } else {
            res.send('-1')
        }
    })

    app.get('/saveAndRestart', async function(req, res) {
        newConfig = JSON.parse(req.query.configs)
        for (var key in newConfig)
            configs[key] = newConfig[key]
        newConfig = JSON.stringify(configs, null, 4)
        if (newConfig) {
            fs.writeFileSync(configPath, newConfig)
            configs = JSON.parse(newConfig)
            console.log("[INFO] Shutting down MixIO Server...")
            await mixio.stop();
            serverStatus = false;
            console.log("[INFO] MixIO Server is already shut down.")
            console.log("[INFO] Starting MixIO Server...")
            mixio = await mixioServer();
            serverStatus = true;
            res.send('1')
        } else
            res.send('-1')
    })

    app.get('/stop', async function(req, res) {
        if (serverStatus) {
            console.log("[INFO] Shutting down MixIO Server...")
            await mixio.stop();
            console.log("[INFO] MixIO Server is already shut down.")
            serverStatus = false
            res.send('1')
        } else {
            res.send('-1')
        }
    })

    app.get('/addAccount', function(req, res2) {
        var userName = req.query.userName
        var password = req.query.password
        var question = req.query.question
        var answer = req.query.answer
        if (userName && password && question && answer) {
            if(configs["MIXIO_HTTP_PORT"] !=0)
                {
                    require('http').get('http://localhost:' + configs["MIXIO_HTTP_PORT"] + "/addAccount?userName=" + userName + "&password=" + password + "&question=" + question + "&answer=" + answer, function(req, res) {
                        var html = '';
                        req.on('data', function(data) {
                            html += data;
                        });
                        req.on('end', function() {
                            res2.send(html)
                        });
                    }).on('error', function() {
                        res2.send('3')
                    })
                }
                else
                    {
                        const https = require('https');
    
                        // 对参数进行编码
                        const encodedPath = "/addAccount?userName=" + encodeURIComponent(userName) + 
                                           "&password=" + encodeURIComponent(password) + 
                                           "&question=" + encodeURIComponent(question) + 
                                           "&answer=" + encodeURIComponent(answer);
                        
                        const options = {
                            hostname: 'localhost',
                            port: configs["MIXIO_HTTPS_PORT"],
                            path: encodedPath,
                            method: 'GET',
                            rejectUnauthorized: false,  // 忽略证书验证
                            agent: false
                        };
                        
                        https.get(options, function(res) {
                            var html = '';
                            res.on('data', function(data) {
                                html += data;
                            });
                            res.on('end', function() {
                                res2.send(html);
                            });
                        }).on('error', function(e) {
                            console.log(e);
                            res2.send('3');
                        });
                    }
        }

    })

    app.get('/resetf', function(req, res2) {
        var userName = req.query.username
        var password = req.query.password
        if (userName && password) {
            if(configs["MIXIO_HTTP_PORT"] !=0)
                {
                    require('http').get('http://localhost:' + configs["MIXIO_HTTP_PORT"] + "/resetf?target=" + userName + "&pass=" + password, function(req, res) {
                        var html = '';
                        req.on('data', function(data) {
                            html += data;
                        });
                        req.on('end', function() {
                            res2.send(html)
                        });
                    }).on('error', function() {
                        res2.send('3')
                    })
                }
                else
                    {
                        const https = require('https');
    
                        // 对参数进行编码
                        const encodedPath = "/resetf?target=" + encodeURIComponent(userName) + 
                                           "&pass=" + encodeURIComponent(password)
                        
                        const options = {
                            hostname: 'localhost',
                            port: configs["MIXIO_HTTPS_PORT"],
                            path: encodedPath,
                            method: 'GET',
                            rejectUnauthorized: false,  // 忽略证书验证
                            agent: false
                        };
                        
                        https.get(options, function(res) {
                            var html = '';
                            res.on('data', function(data) {
                                html += data;
                            });
                            res.on('end', function() {
                                res2.send(html);
                            });
                        }).on('error', function(e) {
                            console.log(e);
                            res2.send('3');
                        });
                    }
        }

    })
    app.get('/deletef', function(req, res2) {
        var userName = req.query.username
        if (userName) {
            if(configs["MIXIO_HTTP_PORT"] !=0)
                {
                    require('http').get('http://localhost:' + configs["MIXIO_HTTP_PORT"] + "/deletef?target=" + userName, function(req, res) {
                        var html = '';
                        req.on('data', function(data) {
                            html += data;
                        });
                        req.on('end', function() {
                            res2.send(html)
                        });
                    }).on('error', function() {
                        res2.send('3')
                    })
                }
                else
                    {
                        const https = require('https');
    
                        // 对参数进行编码
                        const encodedPath = "/deletef?target=" + encodeURIComponent(userName)
                        
                        const options = {
                            hostname: 'localhost',
                            port: configs["MIXIO_HTTPS_PORT"],
                            path: encodedPath,
                            method: 'GET',
                            rejectUnauthorized: false,  // 忽略证书验证
                            agent: false
                        };
                        
                        https.get(options, function(res) {
                            var html = '';
                            res.on('data', function(data) {
                                html += data;
                            });
                            res.on('end', function() {
                                res2.send(html);
                            });
                        }).on('error', function(e) {
                            console.log(e);
                            res2.send('3');
                        });
                    }
        }

    })
    app.get('/start', async function(req, res) {
        if (!serverStatus) {
            console.log("[INFO] Starting MixIO Server...")
            mixio = await mixioServer();
            serverStatus = true
            res.send('1')
        } else
            res.send('-1')
    })

    const VERSION_FILE = 'versions.json';
    function getLocalVersion(product, platform) {
        if(product == "mixly")
        {
            if(fs.existsSync("../mixly"))
            {
                try {
                    if (fs.existsSync(VERSION_FILE)) {
                        return fs.readFileSync(VERSION_FILE, 'utf8');
                    }
                } catch (error) {
                    console.error('读取版本文件失败:', error);
                }
                return '2025.09.06';
            }
            return 'None'
        }
        else if(product == "mixio"){
            return VERSION
        }
    }
    function saveVersionInfo(version) {
        fs.writeFileSync(VERSION_FILE, version);
    }
    async function getCloudVersion(product,platform) {
        try {
            const response = await axios.get('http://update.mixly.cn/index.php');
            return response.data[product][platform];
        } catch (error) {
            console.error('获取云端版本信息失败:', error);
            return "2025.09.06"
        }
    }
    async function checkUpdate(product, platform) {
        try {
            const localVersion = getLocalVersion(product,platform);
            const cloudVersions = await getCloudVersion(product, platform);
            const cloudVersion = cloudVersions["version"]
            const cloudFile = 'http://update.mixly.cn/download.php?file=' + cloudVersions["file"]
            return {
                needsUpdate: localVersion !== cloudVersion,
                localVersion: localVersion,
                cloudVersion: cloudVersion,
                cloudFile: cloudFile,
                error: ""
            };
        } catch (error) {
            return {
                needsUpdate: false,
                localVersion: '',
                cloudVersion: '',
                cloudFile: '',
                error: error.message
            }
        }
    }
    // 检查更新接口
    app.post('/api/check-update', async (req, res) => {
        var product = req.body.product
        var platform = req.body.platform
        const updateInfo = await checkUpdate(product, platform);
        res.json(updateInfo);
    });

    // 下载进度返回
    function deleteFolderRecursive(dirPath) {
        if (fs.existsSync(dirPath)) {
          fs.readdirSync(dirPath).forEach(file => {
            const curPath = path.join(dirPath, file);
            if (fs.lstatSync(curPath).isDirectory()) {
              deleteFolderRecursive(curPath);
            } else {
              fs.unlinkSync(curPath);
            }
          });
          fs.rmdirSync(dirPath);
        }
      }

    app.get('/api/download', async (req, res) => {
        try {
            const {url, cloudVersion} = req.query;
            var filePath, fileStream
            if(url.indexOf(".zip")==-1)
            {
                filePath = "."
                if(process.platform == 'win32')
                {
                    fileStream = fs.createWriteStream(filePath + "/mixio.exe.tmp")
                }
                else
                {
                    fileStream = fs.createWriteStream(filePath + "/mixio.tmp")
                }
            }
            else
            {
                filePath = "../mixly"
                if(fs.existsSync(filePath)){
                    deleteFolderRecursive(filePath)
                }
                fs.mkdirSync("../mixly")
                fileStream = fs.createWriteStream(filePath + "/mixly.zip")
            }
            // 设置响应头
            res.setHeader('Content-Type', 'text/event-stream');
            res.setHeader('Transfer-Encoding', 'chunked');

            // 发起下载请求
            const response = await axios({
                method: 'GET',
                url: url,
                responseType: 'stream'
            });

            const totalSize = parseInt(response.headers['content-length'], 10);
            let downloadedSize = 0;
            var lastProgress = 0;
            // 发送进度信息
            const sendProgress = (progress) => {
                if(progress != lastProgress)
                {
                    res.write('data:' + JSON.stringify({ type: 'progress', progress }) + '\n\n');
                    lastProgress = progress
                }
            };

            // 发送完成信息
            const sendComplete = (version) => {
                res.write('data:' + JSON.stringify({ type: 'complete', version }) + '\n\n');
                res.end();
            };
            response.data.pipe(fileStream);
            // 处理数据流
            response.data.on('data', (chunk) => {
                downloadedSize += chunk.length;
                const progress = Math.round((downloadedSize / totalSize) * 100);
                sendProgress(progress);
            });

            fileStream.on('finish', async () => {
                // 获取版本信息并保存
                if(url.indexOf(".zip")!=-1)
                {
                    saveVersionInfo(cloudVersion);
                    res.write('data:' + JSON.stringify({ type: 'unzip'}) + '\n\n');
                    extract("../mixly/mixly.zip", { dir: path.join(path.dirname(process.execPath), '../mixly') }).then(()=>{
                        sendComplete("mixly");
                    })
                }
                else{
                    sendComplete("mixio");
                    if (process.platform === "win32") {
                        // Windows平台
                        const batScriptContent = `
ping 127.0.0.1 -n 5 >nul
del "mixio.exe"
rename "mixio.exe.tmp" "mixio.exe"
del "%~f0"`;
                        try {
                            // 写入bat脚本
                            fs.writeFileSync('update_mixio.bat', batScriptContent, 'utf8');
                            // 执行bat脚本（异步执行，不等待完成）
                            const child = spawn('update_mixio.bat', [], {
                                stdio: 'ignore',  // 分离后不再需要继承stdio
                                shell: true,
                                windowsHide: true,
                                detached: true,  
                                windowsVerbatimArguments: false
                            });
                            // 解除关联
                            child.unref();
                            setTimeout(() => {
                                process.exit(0);
                            }, 1000);
                            
                        } catch (error) {
                            console.error('创建或执行bat脚本时出错:', error);
                        }
                    } else {
                        // Linux/macOS平台
                            const shScriptContent = `#!/bin/bash
    sleep 5
    rm -f "mixio"
    mv "mixio.tmp" "mixio"
    chmod +x "./mixio"
    rm -- "$0"`;
                            try {
                                // 写入sh脚本
                                fs.writeFileSync('update_mixio.sh', shScriptContent, 'utf8');
                                
                                // 给脚本添加执行权限
                                await fs.chmod('update_mixio.sh', '755');
                                
                                // 执行sh脚本（异步执行，不等待完成）
                                const child = spawn('nohup', ['./update_mixio.sh'], {
                                    stdio: 'ignore',
                                    detached: true,
                                    shell: false
                                });
                                
                                // 解除关联
                                child.unref();
                                
                                // 关闭服务器
                                setTimeout(() => {
                                    process.exit(0);
                                }, 1000);
                                
                            } catch (error) {
                                console.error('创建或执行sh脚本时出错:', error);
                            }
                    }
                }
            });
            response.data.on('error', (error) => {
                res.status(500).json({ error: '下载失败' });
            });


        } catch (error) {
            console.log(error.message)
            res.status(500).json({ error: '下载失败' });
        }
    });


    app.use('/js', express.static(path.join(__dirname, 'js')));

    app.use('/css', express.static(path.join(__dirname, 'css')));

    app.use('/img', express.static(path.join(__dirname, 'img')));

    app.use('/fonts', express.static(path.join(__dirname, 'fonts')));

    app.use('/blockly', express.static(path.join(__dirname, 'blockly')));

    app.use('/icons', express.static(path.join(__dirname, 'icons')));

    app.use('/documentation', express.static(path.join(__dirname, 'documentation')));
    backServer = https.createServer(credentials, app)
    backServer.listen(18084, function() {
        console.log("[INFO] MixIO Admin server listening on port", 18084)
    })
}

var mixioServer = async function() {


    aedes = aedesmodule()
    const httpServer = http.createServer()
    var tasks = {};

    function startHost(userName, projectName, projectPass, callback) {
        db.get("select * from `project` where userName=? and projectName=?", [userName, projectName], function(err, row) {
            if (row) {
                var projectLayout = row["projectLayout"]
                var dataStorage = row["dataStorage"]
                if (dataStorage == null)
                    dataStorage = "{}"
                var logicStorage = row["logicStorage"]
                var code = ""
                var dom = ""
                try {
                    if (projectLayout != null) {
                        var layoutJSON = JSON.parse(projectLayout)["layout_info"]
                        if (layoutJSON && layoutJSON != [])
                            dom = stringendecoder.decodeHtml(layoutJSON)
                    }
                    if (logicStorage != null)
                        code = stringendecoder.decodeHtml(JSON.parse(logicStorage)["code"])
                    else
                        code = ""
                } catch {

                }
                var illegalKeywords = ["for", "while"]
                var isIllegal = false
                for (wordIndex in illegalKeywords) {
                    if (code.indexOf(illegalKeywords[wordIndex]) != -1) {
                        isIllegal = true
                        break
                    }
                }
                if (isIllegal) {
                    callback(JSON.stringify({
                        "code": -3
                    }))
                    return
                }
                var codeFunction = function(obj) {
                    return Function("return(" + obj + ")")()(
                        new MixIOclosure(userName, projectName, projectPass, dataStorage, dom)
                    )
                }
                try {
                    var closure = codeFunction("function(MixIO){ " + code + " return MixIO}")
                    if (tasks[userName])
                        tasks[userName].push({
                            'projectName': projectName,
                            'projectPass': projectPass,
                            'closure': closure
                        })
                    else
                        tasks[userName] = [{
                            'projectName': projectName,
                            'projectPass': projectPass,
                            'closure': closure
                        }]
                        //Normal start
                    callback(JSON.stringify({
                        "code": 1
                    }))
                } catch (e) {
                    console.log(e)
                        //Exception
                    callback(JSON.stringify({
                        "code": -1,
                        "exception": e
                    }))
                }
            } else {
                //Project Not Found
                callback(JSON.stringify({
                    "code": -2
                }))
            }
        })
    }

    function endHost(userName, projectName, callback) {
        if (tasks[userName]) {
            for (userTask in tasks[userName]) {
                if (tasks[userName][userTask]["projectName"] == projectName) {
                    tasks[userName][userTask]["closure"].stop_project()
                    tasks[userName].splice(userTask, 1);
                    callback()
                    break
                }
            }
        }
    }

    function randomString(length, chars) {
        var result = '';
        for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
        return result;
    }

    ws.createServer({
        server: httpServer
    }, aedes.handle)
    const plainServer = require('net').createServer(aedes.handle)

    const httpsServer = https.createServer(credentials)
    ws.createServer({
        server: httpsServer
    }, aedes.handle)


    aedes.authenticate = function(client, username, password, callback) {
        if (username == "MixIO_public" && password == "MixIO_public") {
            client.user = "MixIO"
            callback(null, true)
        } else
            db.get("select password from user where username = ?", [username], function(err, row) {
                var auth = false
                if (err)
                    console.log(err)
                else if (row && (row["password"] == password)) {
                    auth = true
                    client.user = username
                }
                callback(null, auth)
            })

    }
    aedes.authorizePublish = function(client, packet, callback) {
        if (packet.topic == "$SYS/hello")
            return callback(null)
        if (client.user != packet.topic.split('/')[0])
            return callback(new Error('wrong topic'))
        else {
            if (globalConnectionControl[client.id]) {
                if (Date.now() - globalConnectionControl[client.id][0] > 1000) {
                    globalConnectionControl[client.id][0] = Date.now()
                    globalConnectionControl[client.id][1] = 0
                } else if (globalConnectionControl[client.id][1] > MAX_MESSAGE_PER_SECOND) {
                    delete globalConnectionControl[client.id]
                    return callback(new Error('too fast'))
                }
            }
            callback(null)
        }
    }

    aedes.authorizeSubscribe = function(client, subscription, callback) {
        if (client.user != subscription.topic.split('/')[0] && subscription.topic != "$SYS/hello")
            return callback(new Error('wrong topic'))
        else
            callback(null, subscription);
    }

    setInterval(function() {
        aedes.publish({
            cmd: 'publish',
            qos: 0,
            dup: false,
            topic: '$SYS/hello',
            payload: Buffer.from("" + Date.now()),
            retain: false
        })
    }, 10000)

    aedes.on('publish', function(packet, client) {

        if (client) {
            if (globalConnectionControl[client.id])
                globalConnectionControl[client.id][1] = globalConnectionControl[client.id][1] + 1
            else
                globalConnectionControl[client.id] = [Date.now(), 1]
        }

        var topic = packet.topic.split('/')
        var payload = String(packet.payload)
        if (topic.length == 4) {
            if(topic[3][0] == '$') {
                // 判断是否是base64, 开头为data:image/***;base64, ***可以为png,bmp,jpg,jpeg,gif,svg,ico
                const allowFormats = ['png', 'bmp', 'jpg', 'jpeg', 'gif', 'svg', 'ico'];
                const base64Reg = /^data:image\/(\w+);base64,/;
                const match = payload.match(base64Reg);
                if (match && allowFormats.includes(match[1])) {
                    // 是base64
                    const format = match[1];
                    const currentDate = new Date();
                    const timeStamp = `${currentDate.getFullYear()}_${(currentDate.getMonth() + 1).toString().padStart(2, '0')}_${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}_${currentDate.getMinutes().toString().padStart(2, '0')}_${currentDate.getSeconds().toString().padStart(2, '0')}`;
                    const fileName = topic[3].substr(1) + '_' + `${timeStamp}.${format}`;
                    const filePath = path.join('store', topic[0], topic[1], topic[2], fileName);
                    const base64Data = payload.replace(base64Reg, '');
                    const buffer = Buffer.from(base64Data, 'base64');
                    fs.mkdirSync(path.dirname(filePath), { recursive: true });
                    fs.writeFileSync(filePath, buffer);
                } else {
                    // 全部明文存为txt
                    const currentDate = new Date();
                    const timeStamp = `${currentDate.getFullYear()}_${(currentDate.getMonth() + 1).toString().padStart(2, '0')}_${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}_${currentDate.getMinutes().toString().padStart(2, '0')}_${currentDate.getSeconds().toString().padStart(2, '0')}`;
                    const fileName = topic[3].substr(1) + '_' + `${timeStamp}.txt`;
                    const filePath = path.join('store', topic[0], topic[1], topic[2], fileName);
                    fs.mkdirSync(path.dirname(filePath), { recursive: true});
                    fs.writeFileSync(filePath, payload);
                }
            }
        }

        if (topic.length == 3) {
            if(topic[2][0] == '$') {
                // 判断是否是base64, 开头为data:image/***;base64, ***可以为png,bmp,jpg,jpeg,gif,svg,ico
                const allowFormats = ['png', 'bmp', 'jpg', 'jpeg', 'gif', 'svg', 'ico'];
                const base64Reg = /^data:image\/(\w+);base64,/;
                const match = payload.match(base64Reg);
                if (match && allowFormats.includes(match[1])) {
                    // 是base64
                    const format = match[1];
                    const currentDate = new Date();
                    const timeStamp = `${currentDate.getFullYear()}_${(currentDate.getMonth() + 1).toString().padStart(2, '0')}_${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}_${currentDate.getMinutes().toString().padStart(2, '0')}_${currentDate.getSeconds().toString().padStart(2, '0')}`;
                    const fileName = topic[2].substr(1) + '_' + `${timeStamp}.${format}`;
                    const filePath = path.join('store', topic[0], topic[1], fileName);
                    const base64Data = payload.replace(base64Reg, '');
                    const buffer = Buffer.from(base64Data, 'base64');
                    fs.mkdirSync(path.dirname(filePath), { recursive: true });
                    fs.writeFileSync(filePath, buffer);
                } else {
                    // 全部明文存为txt
                    const currentDate = new Date();
                    const timeStamp = `${currentDate.getFullYear()}_${(currentDate.getMonth() + 1).toString().padStart(2, '0')}_${currentDate.getDate().toString().padStart(2, '0')}_${currentDate.getHours().toString().padStart(2, '0')}_${currentDate.getMinutes().toString().padStart(2, '0')}_${currentDate.getSeconds().toString().padStart(2, '0')}`;
                    const fileName = topic[2].substr(1) + '_' + `${timeStamp}.txt`;
                    const filePath = path.join('store', topic[0], topic[1], fileName);
                    fs.mkdirSync(path.dirname(filePath), { recursive: true});
                    fs.writeFileSync(filePath, payload);
                }
            }
            if (topic[2] == 'b640a0ce465fa2a4150c36b305c1c11b') {
                if (STORAGE_ENGINE == "sqlite")
                    db.run("insert or ignore into devices (userName, clientid) values (?,?)", [topic[0], payload])
                else if (STORAGE_ENGINE == "mysql")
                    db.run("insert ignore into devices (userName, clientid) values (?,?)", [topic[0], payload])
            } else if (topic[2] == '9d634e1a156dc0c1611eb4c3cff57276') {
                db.run("delete from devices where userName = ? and clientid = ?", [topic[0], payload])
                if (client)
                    delete globalConnectionControl[client.id]
            } else if (configs["ALLOW_HOOK"] && reserveJSON[topic[0]] && topic[0] != "$SYS") {
                var userName = topic[0]
                var reserveTopic = topic[1] + "/" + topic[2]
                var hash = 0,
                    i, chr;
                for (i = 0; i < userName.length; i++) {
                    chr = userName.charCodeAt(i);
                    hash = ((hash << 5) - hash) + chr;
                    hash |= 0;
                }
                var targetDB = reserveDBs[Math.abs(hash) % 8]
                targetDB.get("select count(*) from `reserve` where userName = ? and topic not like '$%'", [userName, ], function(err, row) {
                    if (err) {
                        console.log(err.message)
                    } else {
                        if (row && row["count(*)"] < MAX_MESSAGE_PER_USER) {
                            targetDB.run("insert into `reserve` (userName, topic, message) values (?,?,?)", [userName, reserveTopic, payload], function(err) {
                                if (err) {
                                    console.log(err.message)
                                }
                            })
                        } else if (row["count(*)"] >= MAX_MESSAGE_PER_USER) {
                            targetDB.get("select id from `reserve` where userName = ? order by id asc limit 1", [userName, ], function(err, row) {
                                if (err) {
                                    console.log(err.message)
                                } else {
                                    if (row && row["id"]) {
                                        targetDB.run("delete from `reserve` where id = ?", [row["id"], ], function(err) {
                                            if (err) {
                                                console.log(err.message)
                                            } else {
                                                targetDB.run("insert into `reserve` (userName, topic, message) values (?,?,?)", [userName, reserveTopic, payload], function(err) {
                                                    if (err) {
                                                        console.log(err.message)
                                                    }
                                                })
                                            }
                                        })
                                    }
                                }
                            })
                        }
                    }
                })
            }
        }
    })

    var app = express();
    app.use(bodyParser.json({
        limit: '50mb'
    }));
    app.use(bodyParser.urlencoded({
        limit: '50mb',
        extended: true
    }));
    app.use(session({
        secret: 'mixio',
        name: 'mixio',
        resave: false,
        rolling: true,
        saveUninitialized: true,
        cookie: {
            path: '/',
            httpOnly: true,
            maxAge: 18000000
        }
    }));

    app.set('trust proxy', 1)

    app.use(cors())

    app.get('/', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/index.ejs', {
            'footer': configs["FOOTER"],
            'mixly': fs.existsSync("../mixly"),
            'mixai': fs.existsSync("../mixai")
        }, function(err, data) {
            res.send(data)
        })
    })

    app.post('/proxy', function(req, res) {
        var url = req.body.url
        var data = req.body.data
            // timeout in 5 seconds
        var timeout = 50000
        request({
            url: url,
            method: "POST",
            json: true,
            headers: {
                "content-type": "application/json",
            },
            body: data,
            timeout: timeout
        }, function(error, response, body) {
            if (!error) {
                res.send(body)
            } else {
                res.send({
                    "status": "failed",
                    "reason": error
                })
            }
        })
    })

    app.get('/index', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/index.ejs', {
            'main': fs.existsSync("config/certs/chain.crt"),
            'mixly': fs.existsSync("../mixly"),
            'mixai': fs.existsSync("../mixai"),
            'configs': configs
        }, function(err, data) {
            res.send(data)
        })
    })

    app.get('/observe', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/observe.ejs', {
            'configs': configs
        }, function(err, data) {
            res.send(data)
        })
    })

    app.get('/host', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/host.ejs', {
            'configs': configs
        }, function(err, data) {
            res.send(data)
        })
    })

    // tinyWebDB Implementation
    function validateRequiredParams(req, res) {
        return new Promise((resolve) => {
            const { user, secret, action } = req.body;
            
            db.get("SELECT * FROM `user` WHERE username = ?", [user], function(err, row) {
                if (err) {
                    return res.json({ status: 'error', message: '数据库查询错误' });
                }
                
                if (row) {
                    if (row.password === secret) {
                        if (!action) {
                            res.json({ status: 'error', message: '缺少操作类型参数' });
                            return resolve(true); // 表示已处理响应
                        }
                        
                        const validActions = ['update', 'get', 'delete', 'count', 'search'];
                        if (!validActions.includes(action)) {
                            res.json({ status: 'error', message: '无效的操作类型' });
                            return resolve(true);
                        }
                        
                        return resolve(false); // 验证通过，未处理响应
                    } else {
                        res.json({ status: 'error', message: '用户名或密钥错误' });
                        return resolve(true);
                    }
                } else {
                    res.json({ status: 'error', message: '用户名或密钥错误' });
                    return resolve(true);
                }
            });
        });
    }
    
    app.post('/tinydb', async (req, res) => {
        const hasError = await validateRequiredParams(req, res);
        // 如果 validateRequiredParams 已经发送了响应，直接返回
        if (hasError) {
            return;
        }
        const { user, action, tag, value } = req.body;
        var hash = 0,
        i, chr;
        for (i = 0; i < user.length; i++) {
            chr = user.charCodeAt(i);
            hash = ((hash << 5) - hash) + chr;
            hash |= 0;
        }
        hash = Math.abs(hash) % 8
        switch (action) {
            case 'update':
                handleUpdate(req, res, hash);
                break;
            case 'get':
                handleGet(req, res, hash);
                break;
            case 'delete':
                handleDelete(req, res, hash);
                break;
            case 'count':
                handleCount(req, res, hash);
                break;
            case 'search':
                handleSearch(req, res, hash);
                break;
            default:
                res.json({ status: 'error', message: '未知操作类型' });
        }
    });
    
    // 更新操作
    function handleUpdate(req, res, hash) {
        const { user, tag, value } = req.body;
        if (!tag || value === undefined) {
            return res.json({ status: 'error', message: '更新操作需要 tag 和 value 参数' });
        }
        reserveDBs[hash].get("SELECT * FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], (err, row) => {
            if (err) {
                return res.json({ status: 'error', message: '数据库查询错误' });
            }
            if (row) {
                reserveDBs[hash].run("UPDATE reserve SET message = ?, time = CURRENT_TIMESTAMP WHERE topic = ? AND userName = ?", 
                       [value, '$' + tag, user], function(err) {
                    if (err) {
                        return res.json({ status: 'error', message: '更新失败' });
                    }
                    res.json({ status: 'success', message: '更新成功' });
                });
            } else {
                // 插入新记录
                reserveDBs[hash].run("INSERT INTO reserve (userName, topic, message) VALUES (?, ?, ?)", 
                       [user, '$' + tag, value], function(err) {
                    if (err) {
                        return res.json({ status: 'error', message: '插入失败' });
                    }
                    res.json({ status: 'success', message: '更新成功' });
                });
            }
        });
    }
    
    // 读取操作
    function handleGet(req, res, hash) {
        const { user, tag } = req.body;
        
        if (!tag) {
            return res.json({ status: 'error', message: '读取操作需要 tag 参数' });
        }
        
        reserveDBs[hash].get("SELECT * FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], (err, row) => {
            if (err) {
                return res.json({ status: 'error', message: '数据库查询错误' });
            }
            
            if (row) {
                res.json({ status: 'success', value: row.message });
            } else {
                res.json({ status: 'success', value: "None" });
            }
        });
    }
    
    // 删除操作
    function handleDelete(req, res, hash) {
        const { user, tag } = req.body;
        
        if (!tag) {
            return res.json({ status: 'error', message: '删除操作需要 tag 参数' });
        }
        
        reserveDBs[hash].run("DELETE FROM reserve WHERE topic = ? AND userName = ?", ['$' + tag, user], function(err) {
            if (err) {
                return res.json({ status: 'error', message: '删除失败' });
            }
            
            if (this.changes > 0) {
                res.json({ status: 'success', message: '删除成功' });
            } else {
                res.json({ status: 'error', message: '变量不存在' });
            }
        });
    }
    
    // 计数操作
    function handleCount(req, res, hash) {
        const { user } = req.body;
        reserveDBs[hash].get("SELECT COUNT(*) as count FROM reserve WHERE userName = ?", [user], (err, row) => {
            if (err) {
                return res.json({ status: 'error', message: '数据库查询错误' });
            }
            res.json({ status: 'success', count: row.count });
        });
    }
    
    // 查询操作
    function handleSearch(req, res, hash) {
        const { no = 1, count = 1, tag = '', type = 'both', user} = req.body;
        const startIndex = Math.max(0, parseInt(no) - 1);
        const limitCount = Math.min(100, parseInt(count)); // 最多返回100条
        
        let sql = "SELECT * FROM reserve WHERE userName = ?";
        let params = [user];
        
        if (tag) {
            sql += " AND topic LIKE ?";
            params.push(`%${tag}%`);
        }
        
        sql += " ORDER BY time DESC LIMIT ? OFFSET ?";
        params.push(limitCount, startIndex);
        
        reserveDBs[hash].all(sql, params, (err, rows) => {
            if (err) {
                console.log(err)
                return res.json({ status: 'error', message: '数据库查询错误' });
            }
            
            // 筛选以$开头的tag，并在返回时去掉$
            const formattedResult = rows
                .filter(row => row.topic && row.topic.startsWith('$')) // 筛选$开头的tag
                .map(row => {
                    const cleanTopic = row.topic.substring(1); // 去掉开头的$
                    
                    if (type === 'tag') {
                        return { tag: cleanTopic };
                    } else if (type === 'value') {
                        return { value: row.message };
                    } else {
                        return { tag: cleanTopic, value: row.message };
                    }
                });
            
            res.json({ 
                status: 'success', 
                data: formattedResult
            });
        });
    }



    app.get('/api/v1/getData', function(req, res) {
        try {
            if (!(req.query.user && req.query.password && req.query.project && req.query.topic)) {
                res.send('{"status":"incorrect params"}')
                return
            }
            var user = req.query.user
            var password = req.query.password
            var project = req.query.project
            var topic = req.query.topic
            var num = 1
            if (globalQPSControl[user]) {
                if (Date.now() - globalQPSControl[user] > 1000) {
                    globalQPSControl[user] = Date.now()
                } else {
                    res.send('{"status":"too fast"}')
                    return
                }
            } else
                globalQPSControl[user] = Date.now()
            if (req.query.num)
                num = req.query.num
            db.get("select password from user where username = ?", [user], function(err, row) {
                if (err) {
                    res.send('{"status":"failed"}')
                } else if ((!row) && (password != "MixIO_public" || user[0] != "@")) {
                    res.send('{"status":"failed"}')
                } else if (((!row) && (password == "MixIO_public" && user[0] == "@")) || (row && (row["password"] == password))) {
                    db.get("select * from project where username = ? and projectName = ?", [user, project], function(err, row) {
                        if (err) {
                            res.send('{"status":"failed"}')
                        } else if (row) {
                            var dataStorage = row["dataStorage"]
                            if (dataStorage == null)
                                dataStorage = "{}"
                            var dataStorageJSON = JSON.parse(dataStorage)
                            if (dataStorageJSON["received"] && dataStorageJSON["received"][topic]) {
                                var data = dataStorageJSON["received"][topic]
                                if (data.length < num)
                                    num = data.length
                                data = data.slice(0, num)
                                for (var i = 0; i < data.length; i++) {
                                    // for each key, if value is int, convert to int; if value is float, convert to float
                                    for (var key in data[i]) {
                                        // if only have digits, convert to int
                                        if (/^\d+$/.test(data[i][key]))
                                            data[i][key] = parseInt(data[i][key])
                                            // if have digits and one dot, convert to float
                                        else if (/^\d+\.\d+$/.test(data[i][key]))
                                            data[i][key] = parseFloat(data[i][key])
                                            // if json string, convert to json
                                        else if (data[i][key].startsWith("{") && data[i][key].endsWith("}"))
                                            try {
                                                data[i][key] = JSON.parse(stringendecoder.decodeHtml(data[i][key]))
                                                for (var key2 in data[i][key]) {
                                                    if (/^\d+$/.test(data[i][key][key2]))
                                                        data[i][key][key2] = parseInt(data[i][key][key2])
                                                    else if (/^\d+\.\d+$/.test(data[i][key][key2]))
                                                        data[i][key][key2] = parseFloat(data[i][key][key2])
                                                }
                                            }
                                        catch (e) {
                                            data[i][key] = data[i][key]
                                        }
                                    }
                                }
                                res.send(JSON.stringify({
                                    "status": "success",
                                    "data": data
                                }))
                            } else
                                res.send('{"status":"success","data":[]}')
                        }
                    })
                } else {
                    res.send('{"status":"failed"}')
                }
            })
        } catch (e) {
            res.send('{"status":"failed"}')
        }

    })


    app.get('/webapps', function(req, res) {
        if (req.session.userName) {
            db.all("select * from `share` where userName=?", [req.session.userName], function(err, rows) {
                if (err)
                    console.log(err)
                else {
                    for (i in rows) {
                        var tmp = new Date(new Date(rows[i]['timeStamp']).getTime() + 28800000)
                        rows[i]['timeStamp'] = "" + tmp.getFullYear() + "-"
                        if (tmp.getMonth() < 9)
                            rows[i]['timeStamp'] += "0"
                        rows[i]['timeStamp'] += (tmp.getMonth() + 1) + "-"
                        if (tmp.getDate() < 10)
                            rows[i]['timeStamp'] += "0"
                        rows[i]['timeStamp'] += tmp.getDate() + " "
                        if (tmp.getHours() < 10)
                            rows[i]['timeStamp'] += "0"
                        rows[i]['timeStamp'] += tmp.getHours() + ":"
                        if (tmp.getMinutes() < 10)
                            rows[i]['timeStamp'] += "0"
                        rows[i]['timeStamp'] += tmp.getMinutes() + ":"
                        if (tmp.getSeconds() < 10)
                            rows[i]['timeStamp'] += "0"
                        rows[i]['timeStamp'] += tmp.getSeconds()
                    }
                    ejs.renderFile(__dirname + '/ejs/apps.ejs', {
                        'rows': rows
                    }, function(err, data) {
                        res.send(data)
                    })
                }
            })

        } else
            res.redirect('/')
    })

    app.get('/register', function(req, res) {
        if (configs["ALLOW_REGISTER"])
            res.sendFile(__dirname + "/" + "ejs/register.html");
        else
            res.send('不允许自助注册！')
    })

    app.get('/forgot', function(req, res) {
        res.sendFile(__dirname + "/" + "ejs/forgot-password.html");
    })

    app.get('/verify', function(req, res) {
        if (req.session.userName && req.session.salt) {
            ejs.renderFile(__dirname + '/ejs/verify.ejs', {
                userName: req.session.userName
            }, function(err, data) {
                res.send(data)
            })
        } else
            res.redirect('/')
    })

    app.get('/android', function(req, res) {
        res.download(__dirname + "/" + "ejs/MixIO.apk")
    })

    app.get('/fetchObserve', function(req, res) {
        if (req.query.sid) {
            var sid = req.query.sid
            db.get("select * from `share` where shareid=?", [sid], function(err, row) {
                if (err) {
                    console.log(err)
                    res.send('-1')
                } else {
                    if (row && row["status"] == 1) {
                        var userName = row["userName"]
                        db.get("select * from `user` where username =?", [userName], function(err, row2) {
                            if (err) {
                                console.log(err)
                                res.send('-1')
                            } else if (row2) {
                                row["projectPass"] = row2["password"]
                                res.send(JSON.stringify(row))
                            } else
                                res.send('-1')
                        })
                    } else
                        res.send('-1')
                }
            })
        } else
            res.send('-1')
    })

    app.get('/reset', function(req, res) {
        if (req.query.target && req.query.vfcode && req.query.pass) {
            db.get("select * from `user` where username=?", [req.query.target], function(err, row) {
                if (err)
                    console.log(err)
                else
                if (row) {
                    if (row["answer"] == req.query.vfcode) {
                        var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                        var password = md5(req.query.pass + salt)
                        db.run("update `user` set password = ?,salt = ? where username=?", [password, salt, req.query.target], function(err) {
                            if (err)
                                console.log(err)
                            else
                                res.send('1')
                        })
                    } else
                        res.send('2')
                } else
                    res.send('2')
            })
        } else
            res.send('2')
    })

    app.get('/resetf', function(req, res) {
        if (req.query.target && req.query.pass) {
            db.get("select * from `user` where username=?", [req.query.target], function(err, row) {
                if (err)
                    console.log(err)
                else
                if (row) {
                    var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                    var password = md5(req.query.pass + salt)
                    db.run("update `user` set password = ?,salt = ?, verified = 0 where username=?", [password, salt, req.query.target], function(err) {
                        if (err)
                            console.log(err)
                        else
                            res.send('1')
                    })
                } else
                    res.send('2')
            })
        } else
            res.send('2')
    })

    app.get('/deletef', function(req, res) {
        if (req.query.target) {
            db.get("delete from `user` where username=?", [req.query.target], function(err, row) {
                if (err)
                    console.log(err)
                else
                    res.send('1')
            })
        } else
            res.send('2')
    })

    app.get('/setProtect', function(req, res) {
        if (req.session.userName && req.query.question && req.query.answer) {
            db.run("update `user` set question=? , answer=? , verified=1 where username=?", [req.query.question, req.query.answer, req.session.userName], function(err) {
                if (err)
                    console.log(err)
                else
                    res.send('1')
            })
        } else
            res.redirect('/')
    })

    app.get('/registerAccount', function(req, res) {
        if (req.query.userName && req.query.password) {
            db.get("select * from `user` where username=?", [req.query.userName], function(err, row) {
                if (err)
                    console.log(err)
                else {
                    if (row) {
                        if (req.session.salt)
                            req.session.salt = undefined
                        res.send('2')
                    } else {
                        var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                        req.session.userName = req.query.userName
                        req.session.salt = salt
                        var password = md5(req.query.password + salt)
                        db.run("insert into `user` (username, password, salt, verified, question, answer) values(?,?,?,0, '', '')", [req.query.userName, password, salt], function(err) {
                            if (err)
                                console.log(err)
                            else
                                res.send('1')
                        })
                    }
                }
            })
        }
    })

    app.get('/addAccount', function(req, res) {
        if (req.query.userName && req.query.password && req.query.question && req.query.answer) {
            db.get("select * from `user` where username=?", [req.query.userName], function(err, row) {
                if (err)
                    console.log(err)
                else {
                    if (row) {
                        if (req.session.salt)
                            req.session.salt = undefined
                        res.send('2')
                    } else {
                        var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                        req.session.userName = req.query.userName
                        req.session.salt = salt
                        var password = md5(req.query.password + salt)
                        db.run("insert into `user` (username, password, salt, verified, question, answer) values(?,?,?,1,?,?)", [req.query.userName, password, salt, req.query.question, req.query.answer], function(err) {
                            if (err)
                                console.log(err)
                            else
                                res.send('1')
                        })
                    }
                }
            })
        }
    })

    app.post('/addAccount', function(req, res) {
        if (req.body.userName && req.body.password) {
            db.get("select * from `user` where username=?", [req.body.userName], function(err, row) {
                if (err)
                    res.send("Internal Error", 500)
                else {
                    if (row) {
                        res.send({
                            "status": "failed",
                            "reason": "user already exists"
                        })
                    } else {
                        var question = req.body.question ? req.body.question : ""
                        var answer = req.body.answer ? req.body.answer : ""
                        var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                        var password = md5(req.body.password + salt)
                        db.run("insert into `user` (username, password, salt, verified, question, answer) values(?,?,?,1,?,?)", [req.body.userName, password, salt, question, answer], function(err) {
                            if (err)
                                res.send("Internal Error", 500)
                            else
                                res.send({
                                    "status": "success"
                                })
                        })
                    }
                }
            })
        } else
            res.send({
                "status": "failed",
                "reason": "bad request"
            })
    })

    app.post('/resetPassword', function(req, res) {
        if (req.body.userName && req.body.oldPassword && req.body.newPassword) {
            db.get("select * from `user` where username=?", [req.body.userName], function(err, row) {
                if (err)
                    res.send("Internal Error", 500)
                else {
                    if (row) {
                        if (row["password"] == md5(req.body.oldPassword + row["salt"])) {
                            var salt = randomString(16, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
                            var password = md5(req.body.newPassword + salt)
                            db.run("update `user` set password=?,salt=? where username=?", [password, salt, req.body.userName], function(err) {
                                if (err)
                                    res.send("Internal Error", 500)
                                else
                                    res.send({
                                        "status": "success"
                                    })
                            })
                        } else
                            res.send({
                                "status": "failed",
                                "reason": "wrong password"
                            })
                    } else {
                        res.send({
                            "status": "failed",
                            "reason": "user not found"
                        })
                    }
                }
            })
        } else
            res.send({
                "status": "failed",
                "reason": "bad request"
            })
    })


    app.get('/getDevices', function(req, res) {
        if (req.session.userName && req.query.userName) {
            var userName = req.query.userName
            db.all("select clientid from devices where userName = ?", [userName], function(err, rows) {
                res.send(JSON.stringify(rows))
            })
        } else
            res.redirect('/')
    })

    app.get('/getImgStore', function(req, res) {
        if (req.session.userName && req.query.projectName){
            var projectName = req.query.projectName
            var isMixly = req.query.isMixly
            // store/username/projectName
            var imgStorePath = path.join('store/' + req.session.userName + "/" + projectName)
            if (isMixly == "true")
            {
                imgStorePath = path.join('store/MixIO/' + req.session.userName.substr(1) + "/" + projectName)
            }
            // 文件名发送列表
            fs.readdir(imgStorePath, function(err, files) {
                res.send(files || [])
            })
        } else
            res.redirect('/')
    })

    app.get('/deleteImgStore', function(req, res) {
        if (req.session.userName && req.query.projectName && req.query.filename){
            var projectName = req.query.projectName
            var filename = req.query.filename
            var isMixly = req.query.isMixly
            // store/username/projectName
            var imgStorePath = 'store/' + req.session.userName + "/" + projectName
            if (isMixly == "true")
            {
                imgStorePath = path.join('store/MixIO/' + req.session.userName.substr(1) + "/" + projectName)
            }
            // 删除文件
            fs.unlink(path.join(imgStorePath, filename), function(err) {
                if (err) {
                    console.log(err)
                }
            })
            res.send({
                "status": "success"
            }
            )
        } else
            res.redirect('/')
    })

    app.get('/clearImgStore', function(req, res) {
        if (req.session.userName && req.query.projectName && req.query.topicName){
            var projectName = req.query.projectName
            var isMixly = req.query.isMixly
            // store/username/projectName
            var imgStorePath = 'store/' + req.session.userName + "/" + projectName
            if (isMixly == "true")
            {
                imgStorePath = path.join('store/MixIO/' + req.session.userName.substr(1) + "/" + projectName)
            }
            // 删除所有开头为$topicName_的文件
            fs.readdir(imgStorePath, function(err, files) {
                if (err) {
                    console.log(err)
                } else {
                    files.forEach(function(file) {
                        if (file.startsWith(req.query.topicName + "_")) {
                            fs.unlink(path.join(imgStorePath, file), function(err) {
                                if (err) {
                                    console.log(err)
                                }
                            })
                        }
                    })
                }
            })
            res.send({
                "status": "success"
            }
            )
        } else
            res.redirect('/')
    })

    app.get('/projects', function(req, res) {
        if (req.session.userName) {
            ejs.renderFile(__dirname + '/ejs/projects.ejs', {
                isMixly: 0,
                userName: req.session.userName,
                projectPass: req.session.projectPass,
                prjid: req.query.prjid ? req.query.prjid : 'no',
                'configs': configs
            }, function(err, data) {
                res.send(data)
            })
        } else
            res.redirect('/')
    })

    app.get('/mqttdata', function(req, res) {
        if (req.session.userName) {
            ejs.renderFile(__dirname + '/ejs/data.ejs', {
                userName: req.session.userName,
                projectPass: req.session.projectPass,
                'configs': configs
            }, function(err, data) {
                res.send(data)
            })
        } else
            res.redirect('/')
    })

    app.get('/tinydata', function(req, res) {
        if (req.session.userName) {
            ejs.renderFile(__dirname + '/ejs/tinydb.ejs', {
                userName: req.session.userName,
                projectPass: req.session.projectPass,
                'configs': configs
            }, function(err, data) {
                res.send(data)
            })
        } else
            res.redirect('/')
    })

    app.get('/projects-mixly', function(req, res) {
        ejs.renderFile(__dirname + '/ejs/projects.ejs', {
            isMixly: 1,
            userName: req.session.userName,
            projectPass: req.session.projectPass,
            count: 0,
            prjid: req.query.prjid ? req.query.prjid : 'no',
            'configs': configs
        }, function(err, data) {
            res.send(data)
        })
    })

    app.post('/importProjects', function(req, res) {
        var userName = req.session.userName
        var projectName = req.body.projectName
        if (userName && projectName) {
            var projectType = req.body.projectType
            var projectLayout = req.body.projectLayout
            var dataStorage = req.body.dataStorage
            var logicStorage = req.body.logicStorage
            db.run("delete from `project` where userName=? and projectName=?", [userName, projectName], function(err) {
                db.get("select COUNT(*) from `project` where userName=?", [userName], function(err, row) {
                    if (err)
                        console.log(err)
                    else
                    if (row["COUNT(*)"] >= configs['MAX_PROJECT_NUM_PER_USER'])
                        res.send('3')
                    else
                        db.run("insert into `project` (projectName,userName,projectLayout,projectType,dataStorage,logicStorage) values(?,?,?,?,?,?)", [projectName, userName, projectLayout, projectType, dataStorage, logicStorage], function(err) {
                            if (err)
                                console.log(err)
                            else
                                res.send('1')
                        })
                })
            })
        }
    })

    app.get('/exportProjects', function(req, res) {
        if (req.session.userName) {
            var userName = req.session.userName
            db.all("select * from `project` where userName=?", [userName], function(err, rows) {
                res.send(JSON.stringify(rows))
            })
        }
    })

    app.get('/getData', function(req, res) {
        if (req.session.userName) {
            var userName = req.session.userName
            var hash = 0,
                i, chr;
            for (i = 0; i < userName.length; i++) {
                chr = userName.charCodeAt(i);
                hash = ((hash << 5) - hash) + chr;
                hash |= 0;
            }
            reserveDBs[Math.abs(hash) % 8].all("select * from `reserve` where userName=? and topic not like '$%'", [req.session.userName], function(err, rows) {
                if (err) {
                    console.log(err)
                } else {
                    if (rows) {
                        res.send({
                            "count": rows.length,
                            "rows": rows,
                            "max": configs['MAX_MESSAGE_PER_USER']
                        })
                    }
                }
            })
        }

    })

    app.get('/getProjects', function(req, res) {
        if (req.session.userName && req.query.page) {
            var pageStart = parseInt(req.query.page) * 8
            db.get("select COUNT(*) from `project` where userName=?", [req.session.userName], function(err, row) {
                var count = row['COUNT(*)']
                db.all("select projectName,projectLayout,timestamp,projectType,userName from `project` where userName=? order by timestamp desc limit ?,8", [req.session.userName, pageStart], function(err, rows) {
                    resrows = JSON.parse(JSON.stringify(rows))
                    for (r in resrows) {
                        resrows[r]['isTask'] = 0
                    }
                    if (tasks[req.session.userName]) {
                        for (r in resrows) {
                            var prjName = resrows[r]['projectName']
                            var userTasks = tasks[req.session.userName]
                            var isTask = 0
                            for (task in userTasks) {
                                if (userTasks[task]["projectName"] == prjName) {
                                    isTask = 1
                                    if (userTasks[task]["closure"].errorMessage != "") {
                                        isTask = resrows[r]['errorMessage'] = userTasks[task]["closure"].errorMessage
                                    }
                                    break
                                }
                            }
                            resrows[r]['isTask'] = isTask
                        }
                    }
                    res.send(JSON.stringify({
                        rows: resrows,
                        count: count
                    }))
                })
            })
        } else
            res.send('-1')
    })

    app.get('/modifyShare', function(req, res) {
        if (req.session.userName && req.query.shareid && req.query.method) {
            if (req.query.method == 2) {
                db.run("delete from `share_key` where share_key=?", [req.query.shareid], function(err) {
                    if (err)
                        console.log(err)
                    else {
                        db.run("delete from `share` where shareid=?", [req.query.shareid], function(err) {
                            if (err) {
                                console.log(err)
                                res.send('2')
                            } else
                                res.send('1')
                        })
                    }
                })
            } else if (req.query.method == 1 || req.query.method == 0) {
                db.run("update `share` set status = ? where shareid=?", [req.query.method, req.query.shareid], function(err) {
                    if (err) {
                        console.log(err)
                        res.send('2')
                    } else
                        res.send('1')
                })
            } else
                res.send('2')
        } else
            res.redirect('/')
    })

    app.get('/share', function(req, res) {
        if (req.session.userName && req.query.projectName) {
            var userName = req.session.userName
            var projectName = req.query.projectName
            var shareid = md5(userName + projectName).substring(0, 6)
            db.run("delete from `share` where userName=? and projectName=?", [req.session.userName, req.query.projectName], function(err) {
                if (err)
                    console.log(err)
                else {
                    db.run("insert into `share` (shareid, userName, projectName, projectLayout, dataStorage, logicStorage) values(?,?,?,(select projectLayout from `project` where userName = ? and projectName = ?),(select dataStorage from `project` where userName = ? and projectName = ?),(select logicStorage from `project` where userName = ? and projectName = ?))", [shareid, userName, projectName, userName, projectName, userName, projectName, userName, projectName], function(err) {
                        if (err)
                            console.log(err)
                        else {
                            db.run("delete from `share_key` where userName=? and projectName=?", [req.session.userName, req.query.projectName], function(err) {
                                if (err)
                                    console.log(err)
                                else {
                                    db.run("insert into `share_key` (userName,projectPass,projectName,share_key) values (?,?,?,?)", [req.session.userName, req.session.projectPass, req.query.projectName, shareid], function(err) {
                                        if (err)
                                            console.log(err)
                                        else
                                            res.send(shareid)
                                    })
                                }
                            })
                        }
                    })
                }
            })
        } else
            res.redirect('/')
    })

    app.get('/getShare', function(req, res) {
        if (req.session.userName && req.query.shareid) {
            db.get("select * from `share` where shareid=?", [req.query.shareid], function(err, row) {
                if (err)
                    console.log(err)
                else {
                    if (row && row["status"] == 1) {
                        var userName = req.session.userName
                        var projectName = row['shareid'];
                        var projectCount = parseInt(row['shareCount']) + 1;
                        projectName = projectName + projectCount
                        db.run("update `share` set shareCount = ? where shareid=?", [projectCount, req.query.shareid], function(err) {
                            if (err) {
                                console.log(err)
                                res.send(err)
                            } else {
                                db.run("insert into `project`(projectName, userName, projectLayout, dataStorage, logicStorage) values(?,?,(select projectLayout from `share` where shareid = ?),(select dataStorage from `share` where shareid = ?),(select logicStorage from `share` where shareid = ?))", [projectName, userName, req.query.shareid, req.query.shareid, req.query.shareid], function(err) {
                                    if (err) {
                                        console.log(err)
                                        res.send(err)
                                    } else
                                        res.send('1')
                                })
                            }
                        })
                    } else
                        res.send('2')
                }
            })
        } else
            res.redirect('/')
    })

    app.post('/getProject', function(req, res) {
        var projectName = req.body.projectName
        if (req.session.userName && projectName) {
            db.get("select * from `project` where userName=? and projectName=?", [req.session.userName, projectName], function(err, row) {
                if (row) {
                    var result = {}
                    result['userName'] = req.session.userName
                    result['projectPass'] = req.session.projectPass
                    result['projectLayout'] = row['projectLayout']
                    result['dataStorage'] = row['dataStorage']
                    result['logicStorage'] = row['logicStorage']
                    result['history'] = []
                    res.send(JSON.stringify(result))
                } else
                    res.send('0')
            })
        } else
            res.redirect('/')
    })

    app.post('/getHostProject', function(req, res) {
        var userName = req.body.userName
        var projectName = req.body.projectName
        var projectPass = req.body.projectPass
        db.get("select * from `project` where userName=? and projectName=?", [userName, projectName], function(err, row) {
            if (row) {
                var result = {}
                result['userName'] = userName
                result['projectPass'] = projectPass
                result['projectLayout'] = row['projectLayout']
                result['dataStorage'] = row['dataStorage']
                result['logicStorage'] = row['logicStorage']
                res.send(JSON.stringify(result))
            } else
                res.send('0')
        })
    })

    app.post('/saveProject', function(req, res) {
        var projectLayout = req.body.layout
        var projectName = req.body.projectName
        var projectType = req.body.projectType
        var dataStorage = req.body.dataStorage
        var logicStorage = req.body.logicStorage
        if (req.session.userName && projectName && projectType && dataStorage && logicStorage && projectLayout) {
            db.run("update `project` set projectLayout=?, dataStorage=?, logicStorage=?, projectType=? where userName=? and projectName=?", [projectLayout, dataStorage, logicStorage, projectType, req.session.userName, projectName], function(err) {
                if (err) {
                    res.send(err)
                } else {
                    res.send('1')
                }
            })
        } else
            res.send('会话已过期')
    })

    app.post('/saveLay', function(req, res) {
        var projectLayout = req.body.layout
        var projectName = req.body.projectName
        var userName = req.body.userName
        if (projectLayout && projectName && userName) {
            db.run("update `project` set projectLayout=? where userName=? and projectName=?", [projectLayout, userName, projectName], function(err) {
                if (err)
                    res.send('-1')
                else
                    res.send('1')
            })
        }
    })

    app.get('/getSession', function(req, res) {
        result = {}
        if (req.session.userName) {
            result['userName'] = req.session.userName
            result['flag'] = true
        } else
            result['flag'] = false
        res.send(JSON.stringify(result))
    })

    app.get('/queryShareKey', function(req, res) {
        if (req.session.userName && req.query.projectName && req.query.projectPass) {
            var userName = req.session.userName
            var projectName = req.query.projectName
            var projectPass = req.query.projectPass
            db.get("select share_key from `share_key` where userName=? and projectPass=? and projectName=?", [userName, projectPass, projectName], function(err, row) {
                if (row) {
                    res.send(JSON.stringify(row))
                } else {
                    res.send('-1')
                }
            })
        } else
            res.redirect('/')
    })


    app.get('/login', function(req, res) {
        var userName = req.query.userName
        var password = req.query.password
        var directLogin = req.query.directLogin
        if (userName && password)
            if (directLogin) {
                db.get("select * from `user` where username=?", [userName], function(err, row) {
                    if (row) {
                        if (row['password'] == md5(password + row['salt'])) {
                            if (row['verified'] == 1) {
                                req.session.userName = row['username']
                                req.session.projectPass = row['password']
                                if (req.session.salt)
                                    req.session.salt = undefined
                                res.redirect('/projects')
                            } else {
                                req.session.userName = row['username']
                                req.session.salt = row['salt']
                                res.redirect('/verify')
                            }
                        } else
                            res.send('Invalid Username or Password')
                    } else
                        res.send('Invalid Username or Password')
                })
            } else {
                db.get("select * from `user` where username=?", [userName], function(err, row) {
                    if (row) {
                        if (row['password'] == md5(password + row['salt'])) {
                            if (row['verified'] == 1) {
                                req.session.userName = row['username']
                                req.session.projectPass = row['password']
                                if (req.session.salt)
                                    req.session.salt = undefined
                                res.send('1')
                            } else {
                                req.session.userName = row['username']
                                req.session.salt = row['salt']
                                res.send('3')
                            }
                        } else
                            res.send('2')
                    } else
                        res.send('2')
                })
            }
        else
            res.send('2')
    })

    app.get('/createProject', function(req, res) {
        if (req.session.userName && req.query.projectName && req.query.projectType) {
            var userName = req.session.userName
            var projectName = req.query.projectName
            var projectType = req.query.projectType
            var projectInfo = '{"layout_info":[]}';
            db.get("select COUNT(*) from `project` where userName=? and projectName=?", [userName, projectName], function(err, row) {
                if (err)
                    console.log(err)
                else
                if (row["COUNT(*)"] > 0)
                    res.send('2')
                else {
                    db.get("select COUNT(*) from `project` where userName=?", [userName], function(err, row) {
                        if (err)
                            console.log(err)
                        else
                        if (row["COUNT(*)"] >= configs['MAX_PROJECT_NUM_PER_USER'])
                            res.send('3')
                        else
                            db.run("insert into `project` (projectName,userName,projectLayout,projectType) values(?,?,?,?)", [projectName, userName, projectInfo, projectType], function(err) {
                                if (err)
                                    console.log(err)
                                else
                                    res.send('1')
                            })
                    })
                }
            })
        } else
            res.redirect('/')
    })

    app.get('/renameProject', function(req, res) {
        if (req.session.userName && req.query.oldProjectName && req.query.newProjectName) {
            var userName = req.session.userName
            var oldProjectName = req.query.oldProjectName
            var newProjectName = req.query.newProjectName
            db.get("select * from `project` where userName=? and projectName=?", [userName, newProjectName], function(err, row) {
                if (err)
                    console.log(err)
                else
                if (row)
                    res.send('2')
                else {
                    db.run("update `project` set projectName=? where userName=? and projectName=?", [newProjectName, userName, oldProjectName], function(err) {
                        if (err)
                            console.log(err)
                        else {
                            res.send('1')
                        }
                    })
                }
            })
        } else
            res.redirect('/')
    })

    app.get('/copyProject', function(req, res) {
        if (req.session.userName && req.query.oldProjectName && req.query.newProjectName) {
            var userName = req.session.userName
            var oldProjectName = req.query.oldProjectName
            var newProjectName = req.query.newProjectName
            db.get("select * from `project` where userName=? and projectName=?", [userName, newProjectName], function(err, row) {
                if (err)
                    console.log(err)
                else
                if (row)
                    res.send('2')
                else {
                    db.get("select COUNT(*) from `project` where userName=?", [userName], function(err, row) {
                        if (err)
                            console.log(err)
                        else
                        if (row["COUNT(*)"] >= configs['MAX_PROJECT_NUM_PER_USER'])
                            res.send('3')
                        else
                            db.run("insert into `project` (userName, projectLayout, dataStorage, logicStorage, projectName) VALUES (?,(select a.projectLayout from (select projectLayout from `project` where userName=? and projectName=?)a),(select a.dataStorage from (select dataStorage from `project` where userName=? and projectName=?)a),(select a.logicStorage from (select logicStorage from `project` where userName=? and projectName=?)a),?)", [userName, userName, oldProjectName, userName, oldProjectName, userName, oldProjectName, newProjectName], function(err) {
                                if (err)
                                    console.log(err)
                                else
                                    res.send('1')
                            })
                    })
                }
            })
        } else
            res.redirect('/')
    })

    app.post('/updateShareContent', function(req, res) {
        if (req.session.userName && req.body.shareid && req.body.projectName && req.body.projectLayout && req.body.dataStorage && req.body.logicStorage) {
            db.run("update `share` set projectLayout=?, dataStorage=?, logicStorage=?, projectName=? where shareid=? and userName=?", [req.body.projectLayout, req.body.dataStorage, req.body.logicStorage, req.body.projectName, req.body.shareid, req.session.userName], function(err) {
                if (err)
                    console.log(err)
                else
                    res.send('1')
            })
        } else
            res.redirect('/')
    })

    app.get('/addShareKey', function(req, res) {
        var rString = md5(req.session.userName + req.query.projectName).substring(0, 6)
        if (req.session.userName && req.query.projectName && req.query.projectPass) {
            db.run("delete from `share` where userName=? and projectName = ?", [req.session.userName, req.query.projectName], function(err) {
                if (err)
                    console.log(err)
                else {
                    db.run("insert into `share` (shareid, userName, projectName, projectLayout, dataStorage, logicStorage) values(?,?,?,(select projectLayout from `project` where userName = ? and projectName = ?),(select dataStorage from `project` where userName = ? and projectName = ?),(select logicStorage from `project` where userName = ? and projectName = ?))", [rString, req.session.userName, req.query.projectName, req.session.userName, req.query.projectName, req.session.userName, req.query.projectName, req.session.userName, req.query.projectName], function(err) {
                        if (err)
                            console.log(err)
                        else {
                            db.run("delete from `share_key` where userName=? and projectPass=? and projectName=?", [req.session.userName, req.query.projectPass, req.query.projectName], function(err) {
                                if (err)
                                    console.log(err)
                                else {
                                    db.run("insert into `share_key` (userName,projectPass,projectName,share_key) values (?,?,?,?)", [req.session.userName, req.query.projectPass, req.query.projectName, rString], function(err) {
                                        if (err) {
                                            console.log(err)
                                            res.send(err)
                                        } else {
                                            res.send(rString)
                                        }
                                    })
                                }
                            })
                        }
                    })
                }
            })

        } else
            res.redirect('/')
    })

    app.get('/removeShareKey', function(req, res) {
        if (req.session.userName && req.query.shareid) {
            db.run("delete from `share` where shareid=?", [req.query.shareid], function(err) {
                if (err)
                    console.log(err)
                else {
                    db.run("delete from `share_key` where share_key=?", [req.query.shareid], function(err) {
                        if (err) {
                            console.log(err)
                            res.send(err)
                        } else {
                            res.send('1')
                        }
                    })
                }
            })
        } else
            res.redirect('/')
    })

    app.get('/getWeather', function(req, res) {
        if (req.query.dsc_code && !configs["OFFLINE_MODE"]) {
            if (globalWeather[req.query.dsc_code] && globalWeather[req.query.dsc_code].time && (new Date().getTime() - globalWeather[req.query.dsc_code].time) < 600000) {
                res.send(globalWeather[req.query.dsc_code].data)
            } else {
                try {
                    if(configs["BAIDU_MAP_SERVER_AK"])
                    {
                        http.get('http://api.map.baidu.com/weather/v1/?district_id=' + req.query.dsc_code + '&data_type=now&ak=' + configs["BAIDU_MAP_SERVER_AK"], function(req2, res2) {
                            var html = ''
                            req2.on('data', function(data) {
                                html += data;
                            });
                            req2.on('end', function() {
                                globalWeather[req.query.dsc_code] = {
                                    time: new Date().getTime(),
                                    data: html
                                }
                                res.send(html)
                            });
                        }).on('error', function(e) {
                            res.send('-1')
                        })
                    }
                    else if(configs["TENCENT_MAP_KEY"])
                    {
                        http.get('http://apis.map.qq.com/ws/weather/v1/?adcode=' + req.query.dsc_code + '&type=now&key=' + configs["TENCENT_MAP_KEY"], function(req2, res2) {
                            var html = ''
                            req2.on('data', function(data) {
                                html += data;
                            });
                            req2.on('end', function() {
                                try{
                                    var newhtml = JSON.parse(html)['result']['realtime'][0]
                                    html = JSON.stringify({
                                        'status': 0,
                                        'result':{
                                            'location':{
                                                'name': newhtml['province'] + newhtml['city'] + newhtml['district']
                                            },
                                            'now':{
                                                'temp': newhtml['infos']['temperature'],
                                                'text': newhtml['infos']['weather'],
                                                'rh': newhtml['infos']['humidity'],
                                                'wind_class': newhtml['infos']['wind_power'],
                                                'wind_dir': newhtml['infos']['wind_direction']
                                            }
                                        }
                                    })
                                    globalWeather[req.query.dsc_code] = {
                                        time: new Date().getTime(),
                                        data: html
                                    }
                                    res.send(html)
                                }
                                catch(e){
                                    res.send('-1')
                                }
                            });
                        }).on('error', function(e) {
                            res.send('-1')
                        })
                    }
                } catch (e) {
                    console.log(e)
                    res.send('-1')
                }
            }
        } else
            res.send('-1')
    })
    app.get('/api/getWeather', function(req, res) {
        if (req.query.city_code && !configs["OFFLINE_MODE"]) {
            req.query.dsc_code = req.query.city_code.replace("CH", "")
            if (globalWeather[req.query.dsc_code] && globalWeather[req.query.dsc_code].time && (new Date().getTime() - globalWeather[req.query.dsc_code].time) < 600000) {
                res.send(globalWeather[req.query.dsc_code].data)
            } else {
                try {
                    if(configs["BAIDU_MAP_SERVER_AK"])
                    {
                        http.get('http://api.map.baidu.com/weather/v1/?district_id=' + req.query.dsc_code + '&data_type=now&ak=' + configs["BAIDU_MAP_SERVER_AK"], function(req2, res2) {
                            var html = ''
                            req2.on('data', function(data) {
                                html += data;
                            });
                            req2.on('end', function() {
                                globalWeather[req.query.dsc_code] = {
                                    time: new Date().getTime(),
                                    data: html
                                }
                                res.send(html)
                            });
                        }).on('error', function(e) {
                            res.send('-1')
                        })
                    }
                    else if(configs["TENCENT_MAP_KEY"])
                    {
                        http.get('http://apis.map.qq.com/ws/weather/v1/?adcode=' + req.query.dsc_code + '&type=now&key=' + configs["TENCENT_MAP_KEY"], function(req2, res2) {
                            var html = ''
                            req2.on('data', function(data) {
                                html += data;
                            });
                            req2.on('end', function() {
                                try{
                                    var newhtml = JSON.parse(html)['result']['realtime'][0]
                                    html = JSON.stringify({
                                        'status': 0,
                                        'result':{
                                            'location':{
                                                'name': newhtml['province'] + newhtml['city'] + newhtml['district']
                                            },
                                            'now':{
                                                'temp': newhtml['infos']['temperature'],
                                                'text': newhtml['infos']['weather'],
                                                'rh': newhtml['infos']['humidity'],
                                                'wind_class': newhtml['infos']['wind_power'],
                                                'wind_dir': newhtml['infos']['wind_direction']
                                            }
                                        }
                                    })
                                    globalWeather[req.query.dsc_code] = {
                                        time: new Date().getTime(),
                                        data: html
                                    }
                                    res.send(html)
                                }
                                catch(e){
                                    res.send('-1')
                                }
                            });
                        }).on('error', function(e) {
                            res.send('-1')
                        })
                    }
                } catch (e) {
                    console.log(e)
                    res.send('-1')
                }
            }
        } else
            res.send('-1')
    })
    app.get("/api/getCurrentTime", function(req, res) {
        var sysTime = new Date();
        var year = sysTime.getFullYear();
        var month = sysTime.getMonth() + 1; // getMonth() 返回的月份是从0开始的
        var day = sysTime.getDate();
        var hours = sysTime.getHours();
        var minutes = sysTime.getMinutes();
        var seconds = sysTime.getSeconds();

        // 补零操作
        month = month < 10 ? '0' + month : month;
        day = day < 10 ? '0' + day : day;
        hours = hours < 10 ? '0' + hours : hours;
        minutes = minutes < 10 ? '0' + minutes : minutes;
        seconds = seconds < 10 ? '0' + seconds : seconds;

        sysTime = year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
        res.send(JSON.stringify({
            "sysTime": sysTime
        }));
    });

    app.post('/deleteProject', function(req, res) {
        if (req.session.userName && req.body.projectName) {
            db.run("delete from `project` where userName=? and projectName=?", [req.session.userName, req.body.projectName], function(err) {
                if (err)
                    res.send('2')
                else
                    res.send('1')
            })
        } else
            res.redirect('/')
    })

    app.get('/resetQuestion', function(req, res) {
        if (req.query.target) {
            db.get("select * from `user` where username=?", [req.query.target], function(err, row) {
                if (err || !row)
                    res.send({
                        'code': 999,
                        'question': ''
                    })
                else
                    res.send({
                        'code': 1,
                        'question': row['question']
                    })
            })
        }
    })


    app.get('/logout', function(req, res) {
        req.session.destroy(function(err) {
            res.redirect('/');
        })
    })

    app.get('/keyLogin', function(req, res) {
        if (req.query.userName) {
            req.session.userName = '@' + req.query.userName
            req.session.projectPass = 'MixIO_public'
            db.get("select COUNT(*) from `project` where username=?", [req.session.userName], function(err, row) {
                if (err) {
                    console.log(err)
                    res.send('-1')
                } else {
                    if (row["COUNT(*)"] > 0) {
                        res.send('1')
                    } else {
                        var layout = '{"layout_info":[]}';
                        db.run("insert into `project` (projectName,userName,projectLayout,projectType) values('default',?,?,'1')", [req.session.userName, layout], function(err) {
                            if (err) {
                                console.log(err)
                                res.send('-1')
                            } else
                                res.send('1')
                        })
                    }
                }
            })
        } else
            res.send('-1')
    })

    app.get('/time.php', function(req, res) {
        var date = new Date()
        var day = date.getDay() - 1
        if (day < 0)
            day = 6
        res.send([date.getFullYear(), date.getMonth() + 1, date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), day].join(','))
    })

    app.get('/mixio-php/sharekey.php', function(req, res) {
        if (req.query.sk) {
            db.get("select userName,userName as '0', projectName, projectName as '1' ,projectPass, projectPass as '2' from `share_key` where share_key = ?", [req.query.sk], function(err, row) {
                if (err)
                    console.log(err)
                else {
                    if (row)
                        res.send(row)
                    else
                        res.send('-1')
                }
            })
        } else
            res.send('-1')
    })

    app.get('/devAPI', function(req, res) {
        res.sendFile(__dirname + "/ejs/" + "dev.html");
    })

    app.get('/startHost', function(req, res) {
        var userName = req.session.userName
        var projectName = req.query.projectName
        var projectPass = req.session.projectPass
        if (userName && projectName && projectPass)
            startHost(userName, projectName, projectPass, function(status) {
                res.send(status)
            })
        else
            res.send('-1')
    })

    app.get('/queryHook', function(req, res) {
        if (req.session.userName) {
            if (reserveJSON[req.session.userName])
                res.send('1')
            else
                res.send('2')
        } else {
            res.send('0')
        }
    })

    var filterPath = "storage/reserve/filter.json"
    if (!fs.existsSync(filterPath))
        filterPath = path.join(__dirname, filterPath)
    app.get('/startHook', function(req, res) {
        if (req.session.userName) {
            reserveJSON[req.session.userName] = true
            fs.writeFileSync(filterPath, JSON.stringify(reserveJSON, false, 4))
            res.send('1')
        } else {
            res.send('0')
        }
    })

    app.get('/stopHook', function(req, res) {
        if (req.session.userName) {
            reserveJSON[req.session.userName] = false
            fs.writeFileSync(filterPath, JSON.stringify(reserveJSON, false, 4))
            res.send('1')
        } else {
            res.send('0')
        }
    })

    app.get('/clearHook', function(req, res) {
        if (req.session.userName) {
            var condition = req.query.condition
            var userName = req.session.userName
            var hash = 0,
                i, chr;
            for (i = 0; i < userName.length; i++) {
                chr = userName.charCodeAt(i);
                hash = ((hash << 5) - hash) + chr;
                hash |= 0;
            }
            reserveDBs[Math.abs(hash) % 8].run("delete from `reserve` where userName = ? and " + condition, [userName, ], function(err) {
                if (err) {
                    console.log(err.message)
                    res.send('-1')
                } else
                    res.send('1')
            })
        } else
            res.send('0')
    })

    app.get('/endHost', function(req, res) {
        var userName = req.session.userName
        var projectName = req.query.projectName
        if (userName && projectName) {
            endHost(userName, projectName, function() {
                res.send('1')
            })
        } else
            res.send('-1')
    })


    app.use('/js', express.static(path.join(__dirname, 'js')));

    app.use('/css', express.static(path.join(__dirname, 'css')));

    app.use('/img', express.static(path.join(__dirname, 'img')));

    app.use('/store', express.static('store'));

    app.use('/fonts', express.static(path.join(__dirname, 'fonts')));

    app.use('/blockly', express.static(path.join(__dirname, 'blockly')));

    app.use('/icons', express.static(path.join(__dirname, 'icons')));

    app.use('/documentation', express.static(path.join(__dirname, 'documentation')));

    var mixlyPath = "../mixly"
    if (fs.existsSync(mixlyPath)) {
        app.use('/mixly', express.static(mixlyPath));
    }
    var mixaiPath = "../mixai"
    if (fs.existsSync(mixaiPath)) {
        app.use('/mixai', express.static(mixaiPath));
    }



    var reserveJSON = JSON.parse(fs.readFileSync(filterPath), "utf8")

    var oldListen = app.listen
    app.listen = function(port, callback) {
        if (port == 0)
            callback()
        else
            oldListen.call(app, port, callback)
    }

    return new Promise(resolve => {
        plainServer.listen(configs["MIXIO_MQTT_PORT"], function() {
            console.log('[INFO] Plain MQTT server listening on port', configs["MIXIO_MQTT_PORT"])
            httpServer.listen(configs["MIXIO_WS_PORT"], function() {
                console.log('[INFO] WebSocket MQTT server listening on port', configs["MIXIO_WS_PORT"])
                httpsServer.listen(configs["MIXIO_WSS_PORT"], function() {
                    console.log('[INFO] WebSocketS MQTT server listening on port', configs["MIXIO_WSS_PORT"])
                    httpServer2 = http.createServer(app)
                    httpServer2.listen(configs['MIXIO_HTTP_PORT'], function() {
                        if (configs['MIXIO_HTTP_PORT'] != 0)
                            console.log("[INFO] MixIO server listening on port", configs['MIXIO_HTTP_PORT'])
                        httpsServer2 = https.createServer(credentials, app)
                        httpsServer2.listen(configs['MIXIO_HTTPS_PORT'], function() {
                            if (configs['MIXIO_HTTPS_PORT'] != 0)
                                console.log("[INFO] MixIO server (HTTPS) listening on port", configs['MIXIO_HTTPS_PORT'])
                            var stopFunction = function() {
                                return new Promise(resolve => {
                                    //MQTT
                                    plainServer.close(function() {
                                        console.log("[INFO] Plain MQTT server closed")
                                            //MQTT Websocket
                                        httpServer.close(function() {
                                            console.log("[INFO] WebSocket MQTT server closed")
                                                //MixIO HTTP
                                            httpServer2.close(function() {
                                                console.log("[INFO] MixIO server closed")
                                                    //MQTT WebsocketS
                                                httpsServer.close(function() {
                                                    console.log("[INFO] WebSocketS MQTT server closed")
                                                        //MixIO HTTPS
                                                    httpsServer2.close(function() {
                                                        console.log("[INFO] MixIO server (HTTPS) closed")
                                                        resolve("1")
                                                    })
                                                })
                                            })
                                        })
                                    })
                                })
                            }
                            if (STORAGE_ENGINE == 'sqlite') {
                                var dbPath = "storage/mixio.db"
                                db = new sqlite3.Database(
                                    dbPath,
                                    sqlite3.OPEN_READWRITE,
                                    function(err) {
                                        if (err) {
                                            console.log(err.message)
                                        }
                                        db.run('delete from devices')
                                        console.log('[INFO] Storage Engine: SQLite')
                                        console.log('[INFO] Database Connected!')
                                        resolve({
                                            stop: stopFunction
                                        })
                                    }
                                )
                                reserveDBs = []
                                for (var i = 1; i <= 8; i = i + 1) {
                                    var dbPath = "storage/reserve/" + i + ".db"
                                    if (!fs.existsSync(dbPath)) {
                                        dbPath = path.join(__dirname, './reserve/' + i + ".db")
                                    }
                                    reserveDBs.push(
                                        new sqlite3.Database(
                                            dbPath,
                                            sqlite3.OPEN_READWRITE,
                                            function(err) {
                                                if (err)
                                                    console.log(err.message)
                                            }
                                        )
                                    )
                                }
                            } else if (STORAGE_ENGINE == 'mysql') {
                                db = mysql.createConnection({
                                    host: MYSQL_HOST,
                                    port: MYSQL_PORT,
                                    user: MYSQL_USER,
                                    password: MYSQL_PASS
                                })
                                db.get = function(sql, params, callback) {
                                    db.query(sql, params, function(err, rows) {
                                        if (err) {
                                            callback(err, null)
                                        } else {
                                            callback(null, rows[0])
                                        }
                                    })
                                }
                                db.run = function(sql, params, callback) {
                                    db.query(sql, params, function(err, result) {
                                        if (err) {
                                            if (callback) {
                                                callback(err)
                                            }
                                        } else if (callback)
                                            callback()
                                    })
                                }
                                db.all = function(sql, params, callback) {
                                        db.query(sql, params, function(err, rows) {
                                            if (err) {
                                                callback(err, null)
                                            } else {
                                                callback(null, rows)
                                            }
                                        })
                                    }
                                    // create database if not exists
                                db.query('create database if not exists ' + MYSQL_DB, function(err) {
                                    if (err) {
                                        console.log(err.message)
                                    }
                                    db.query('use ' + MYSQL_DB, function(err) {
                                        if (err) {
                                            console.log(err.message)
                                        }
                                        init_mysql(function(status, reason) {
                                            if (status == "error")
                                                console.log(reason)
                                            else if (status == "success") {
                                                console.log("[INFO] Database Initialized!")
                                                db.query('delete from devices')
                                                console.log('[INFO] Storage Engine: MySQL (' + MYSQL_HOST + ')')
                                                console.log('[INFO] Database Connected!')
                                                resolve({
                                                    stop: stopFunction
                                                })
                                            }
                                        })
                                    })
                                })
                                reserveDBs = [db, db, db, db, db, db, db, db]
                            }
                        });
                    })
                })
            })
        })
    })

}

function init_mysql(cb) {
    db.query(`CREATE TABLE IF NOT EXISTS devices (
        userName	VARCHAR(255),
        clientid	VARCHAR(255),
        timestamp	timestamp DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY(clientid)
    )`, function(err, result) {
        if (err) {
            cb("error", err)
        } else {
            db.query(`CREATE TABLE IF NOT EXISTS project (
                projectName	VARCHAR(255),
                userName	VARCHAR(255),
                projectLayout	MEDIUMTEXT,
                dataStorage	MEDIUMTEXT,
                logicStorage	MEDIUMTEXT,
                timestamp	timestamp DEFAULT CURRENT_TIMESTAMP,
                projectType	INTEGER
            )`, function(err, result) {
                if (err) {
                    cb("error", err)
                } else {
                    db.query(`CREATE TABLE IF NOT EXISTS share (
                        shareid VARCHAR(255),
                        userName	VARCHAR(255),
                        projectName	VARCHAR(255),
                        projectLayout	MEDIUMTEXT,
                        dataStorage	MEDIUMTEXT,
                        logicStorage	MEDIUMTEXT,
                        timeStamp timestamp DEFAULT CURRENT_TIMESTAMP,
                        status	INTEGER DEFAULT 1,
                        shareCount	INTEGER DEFAULT 0
                    )`, function(err, result) {
                        if (err) {
                            cb("error", err)
                        } else {
                            db.query(`CREATE TABLE IF NOT EXISTS share_key (
                                userName	VARCHAR(255),
                                projectPass	VARCHAR(255),
                                projectName	VARCHAR(255),
                                share_key	VARCHAR(255)
                            )`, function(err, result) {
                                if (err) {
                                    cb("error", err)
                                } else {
                                    db.query(`CREATE TABLE IF NOT EXISTS user (
                                        id	INTEGER AUTO_INCREMENT,
                                        username	VARCHAR(255),
                                        password	VARCHAR(255),
                                        salt	VARCHAR(255),
                                        is_superuser	INTEGER DEFAULT 0,
                                        verified	INTEGER DEFAULT 1,
                                        question	VARCHAR(255),
                                        answer	VARCHAR(255),
                                        PRIMARY KEY(id)
                                    )`, function(err, result) {
                                        if (err) {
                                            cb("error", err)
                                        } else {
                                            db.query(`CREATE TABLE IF NOT EXISTS reserve (
                                                id	INTEGER AUTO_INCREMENT,
                                                userName	VARCHAR(255),
                                                topic	VARCHAR(255),
                                                message	VARCHAR(1023),
                                                time timestamp DEFAULT CURRENT_TIMESTAMP,
                                                PRIMARY KEY(id)
                                            )`, function(err, result) {
                                                if (err) {
                                                    cb("error", err)
                                                } else {
                                                    cb("success", null)
                                                }
                                            })
                                        }
                                    })
                                }
                            })
                        }
                    })
                }
            })
        }
    })
}


async function startOnce() {
    mixio = await mixioServer()
}

const args = process.argv.slice(2)

var startMixIO = function() {
    var parent_exit = function(child) {
            var logFile = fs.openSync(logFileName, 'r')
            while (true) {
                // check log file for database connection
                var data = fs.readFileSync(logFile, 'utf8')
                if (data[data.length - 1] == "\n")
                    data = data.slice(0, -1)
                if (data != "")
                    console.log(data)
                if (data.toString().indexOf("Database Connected!") != -1) {
                    console.log("MixIO server is running now.")
                    child.unref()
                    for (var t = Date.now(); Date.now() - t <= 2000;);
                    process.exit()
                } else if (data.toString().indexOf("Error") != -1) {
                    console.error("An error occured while initializing MixIO server. Log file: " + process.cwd() + logFileName)
                    child.unref()
                    for (var t = Date.now(); Date.now() - t <= 2000;);
                    process.exit()
                }
            }
        }
        // child process to run 'mixio' in background
    var logFile = fs.openSync(logFileName, 'a')
    if (process.argv[0].indexOf("node") != -1) {
        var child = spawn(process.argv[0], [process.argv[1], "debug"], { detached: true, stdio: ['ignore', logFile, logFile] })
        parent_exit(child)
    } else {
        var child = spawn(process.argv[0], [process.argv[1], "debug"], { detached: true, stdio: ['ignore', logFile, logFile] })
        parent_exit(child)
    }
}

var stopMixIO = function() {
    // try use pid.info to stop process
    try {
        var pid = fs.readFileSync("pid.info", 'utf8')
        if (pid != "") {
            // convert to int
            pid = parseInt(pid)
            process.kill(pid, 'SIGTERM')
            console.log("MixIO server with PID " + pid + " is stopped.")
        } else {
            console.log("MixIO server is not running.")
        }
    } catch (e) {
        console.log("MixIO server is not running.")
    }
}

init(function(res) {
    if (res) {
        configPath = "config/config.json"
        configs = fs.readFileSync(configPath);
        configs = JSON.parse(configs.toString());
        if (!configs["FOOTER"])
            configs["FOOTER"] = ""
        STORAGE_ENGINE = configs["STORAGE_ENGINE"]


        MYSQL_HOST = configs["MYSQL_HOST"]
        MYSQL_USER = configs["MYSQL_USER"]
        MYSQL_PASS = configs["MYSQL_PASS"]
        MYSQL_DB = configs["MYSQL_DB"]
        MYSQL_PORT = configs["MYSQL_PORT"]

        MAX_MESSAGE_PER_USER = configs["MAX_MESSAGE_PER_USER"]
        MAX_MESSAGE_PER_SECOND = configs["MAX_MESSAGE_PER_SECOND"]
        HTTPS_CRT_FILE = configs["HTTPS_CRT_FILE"]
        HTTPS_PRIVATE_PEM = configs["HTTPS_PRIVATE_PEM"]
        if (args.length > 1 || (args.length == 0 && process.platform != "win32")) {
            console.log("Invalid parameter(s). Use \"mixio help\" for help.")
        } else {
            var show = function() {
                if (args.length == 0) {
                    // wait for user input, 1 for start, 2 for stop, 3 for autoStart, 4 for remove autoStart
                    console.log("1. Start MixIO server")
                    console.log("2. Stop MixIO server")
                    console.log("3. Set MixIO server to auto start")
                    console.log("4. Remove MixIO server from auto start")
                    console.log("5. Exit")
                    var rl = readline.createInterface({
                        input: process.stdin,
                        output: process.stdout
                    })
                    rl.question("Please select an option: ", function(answer) {
                        rl.close()
                        if (answer == "1") {
                            startMixIO()
                        } else if (answer == "2") {
                            stopMixIO()
                        } else if (answer == "3") {
                            var child = spawn("schtasks", ["/create", "/sc", "onlogon", "/tn", "MixIO", "/tr", process.execPath + " start", "/rl", "highest", "/f"])
                            child.stdout.on('data', function(data) {
                                // encode to ANSI
                                console.log(iconv.decode(data, 'cp936').toString())
                            })
                            child.stderr.on('data', function(data) {
                                // encode to ANSI
                                console.log(iconv.decode(data, 'cp936').toString())
                            })
                            child.on("close", function() {
                                show();
                            })


                        } else if (answer == "4") {
                            var child = spawn("schtasks", ["/delete", "/tn", "MixIO", "/f"])
                            child.stdout.on('data', function(data) {
                                // encode to ANSI
                                console.log(iconv.decode(data, 'cp936').toString())
                            })
                            child.stderr.on('data', function(data) {
                                // encode to ANSI
                                console.log(iconv.decode(data, 'cp936').toString())
                            })
                            child.on("close", function() {
                                show();
                            })
                        } else if (answer == "5") {
                            process.exit()
                        } else {
                            console.log("Invalid option.")
                        }
                    })
                } else if (args[0] == "debug") {
                    // 记录当前的进程ID
                    console.log("[INFO] MixIO server is running with PID " + process.pid)
                        // 输出到当前目录下的pid.info文件
                    fs.writeFileSync("pid.info", "" + process.pid)
                    if (res) {
                        daemon_start().then(()=>{
                            startOnce()
                        })
                    }
                } else if (args[0] == "start") {
                    startMixIO()

                } else if (args[0] == "stop") {
                    stopMixIO()
                } else if (args[0] == "version") {
                    console.log(VERSION)
                } else if (args[0] == "install" && process.platform == "linux") {
                    var install_shell = `
                        service="
                        [UNIT]
                        Description=MixIO.Service
                        After=network.target
                        StartLimitIntervalSec=0
                        
                        [Service]
                        Type=forking
                        Restart=always
                        RestartSec=1
                        WorkingDirectory="` + process.argv[0].slice(0, process.argv[0].lastIndexOf("/")) + `"
                        ExecStart=` + process.argv[0].slice(0, process.argv[0].lastIndexOf("/")) + `/mixio start
                        ExecStop=` + process.argv[0].slice(0, process.argv[0].lastIndexOf("/")) + `/mixio stop
                        
                        [Install]
                        WantedBy=multi-user.target
                        "
                        echo "$` + `{service}" > /etc/systemd/system/mixio.service
                    `
                        //output shell script to install.sh
                    fs.writeFileSync("install.sh", install_shell)
                        //run install.sh
                    exec('sh install.sh', function(err, stdout, stderr) {
                        if (err) {
                            console.log(err)
                        } else {
                            console.log(stdout)
                        }
                    })
                } else if (args[0] == "help") {
                    console.log("MixIO server help:")
                    console.log("mixio start: start MixIO server in background.")
                    console.log("mixio stop: stop MixIO server.")
                    console.log("mixio debug: start MixIO server in foreground.")
                    console.log("mixio version: show MixIO server version.")
                    if (process.platform == "linux")
                        console.log("mixio install: install MixIO service.")
                } else {
                    console.log("Invalid parameter(s). Use \"mixio help\" for help.")
                }
            }
            show();
        }
    }
})



//MixIO

curlong = 0
curlati = 0

function MixIOLogicError(message) {
    this.message = message
    this.name = "MixIOLogicError"
}

MixIOLogicError.prototype = new Error()

var MixIOclosure = function(userName, projectName, projectPass, dataStorage, dom) {
    var that = this
    this.errorMessage = ""
    this.dataSave = JSON.parse(dataStorage)
    if (!this.dataSave || !this.dataSave["received"]) {
        this.dataSave = {
            "received": {}
        }
    }
    this.lastPublishTime = [new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0), new Date(0)],
        this.minPublishInterval = 500,
        this.client = mqtt.connect('ws://localhost:' + configs["MIXIO_WS_PORT"], {
            'clientId': "MixIO_" + Math.random().toString(16).substr(2, 8),
            'username': userName,
            'password': projectPass
        }).on('connect', function() {
            this.subscribe(userName + "/" + projectName + "/" + "#")
        }).on('message', function(topic, message) {
            var saveTopic = topic.split('/').pop()
            var saveMessage = stringendecoder.encodeHtml(String(message))
            var addJSON = {
                "时间": timeStamp2String(),
                "值": saveMessage
            }
            if (saveTopic in that.dataSave["received"]) {
                that.dataSave["received"][saveTopic].unshift(addJSON)
            } else {
                that.dataSave["received"][saveTopic] = [addJSON]
            }
        }),
        this.publish = function(topic, message) {
            var newPublishTime = new Date()
            if (newPublishTime - this.lastPublishTime[0] >= this.minPublishInterval) {
                this.client.publish(userName + '/' + projectName + '/' + topic.toString(), message.toString())
                this.lastPublishTime.shift()
                this.lastPublishTime.push(new Date())
            } else {
                that.stop_project()

            }
        },
        this.activedom = (new JSDOM("<div id='grid'></div>")).window,
        this.$ = function(activedom) {
            return jq(activedom)
        }(this.activedom),
        this.isRunning = true,
        this.listeners_to_be_removed = [],
        this.timers_to_be_removed = [],
        this.cycles_to_be_removed = [],
        this.stop_project = function() {
            that.isRunning = false
            this.client.end()
            this.safe_pause()
            db.run("update `project` set dataStorage=? where userName=? and projectName=?", [JSON.stringify(that.dataSave), userName, projectName], function(err) {})
        },
        this.safe_pause = function() {
            this.isRunning = false
            for (listener in this.listeners_to_be_removed) {
                for (eventIndex in this.client._events.message) {
                    if (this.client._events.message[eventIndex] == this.listeners_to_be_removed[listener]) {
                        this.client._events.message.splice(eventIndex, 1)
                        break;
                    }
                }
            }
            for (timer in this.timers_to_be_removed) {
                clearTimeout(this.timers_to_be_removed[timer])
            }
            for (cycle in this.cycles_to_be_removed) {
                clearInterval(this.cycles_to_be_removed[cycle])
            }
            this.listeners_to_be_removed = []
            this.timers_to_be_removed = []
            this.cycles_to_be_removed = []
            for (tagIndex in this.eventTags)
                this.$('*[user-title]').unbind(this.eventTags[tagIndex])
        },

        /*合法的MixIO组件种类*/
        this.typeTags = {
            BUTTON: 1,
            SLIDER: 2,
            KEYBOARD: 3,
            JOYSTICK: 4,
            RGB_PICKER: 5,
            BULB: 6,
            TEXT_SCREEN: 7,
            LINE_CHART: 8,
            BAR_CHART: 9,
            DATA_TABLE: 10,
            DASHBOARD: 11,
            DATA_MAP: 12,
            WEATHER: 13
        },

        this.oldTags = ["input_button", "input_slider", "input_keyboard", "input_controller", "input_rgb", "output_bulb", "output_text",
            "output_chart", "output_bar", "table", "output_dashboard", "output_map", "input_weather"
        ],
        this.zhcnTags = ["按键/开关", "滑杆", "文本输入", "摇杆手柄", "RGB色盘", "指示灯", "文本显示屏", "折线图表", "柱状图表", "数据表格", "仪表盘", "数据地图", "实时气象仪"],
        this.log = function() {},
        /*合法的MixIO事件种类*/
        this.eventTags = {
            MQTT_MESSAGE_RECEIVED: "11", //收到MQTT消息

            BUTTON_PRESSED: "111", //按键被按下
            BUTTON_LOOSED: "112", //按键/开关被松开
            BUTTON_CHANGED: "113", //按键/开关收到消息

            SLIDER_SLIDED: "211", //滑杆被拖动
            SLIDER_CHANGED: "212", //滑杆收到消息

            KEYBOARD_SENT: "311", //键盘发送消息

            JOYSTICK_CHANGED: "411", //摇杆被拖动

            RGB_PICKER_PICKED: "511", //RGB色盘被选色
            RGB_PICKER_CHANGED: "512", //RGB色盘收到消息

            BULB_CHANGED: "611", //指示灯收到消息

            TEXT_SCREEN_CHANGED: "711", //文本显示屏收到消息

            LINE_CHART_CHANGED: "811", //折线图表收到消息

            BAR_CHART_CHANGED: "911", //柱状图表收到消息

            DATA_TABLE_CHANGED: "1011", //数据表格收到消息

            DASHBOARD_CHANGED: "1111", //仪表盘收到消息

            DATA_MAP_CHANGED: "1211", //数据地图收到消息

            WEATHER_SYNCED: "1311", //气象仪更新数据
            WEATHER_SENT: "1312" //气象仪发送数据

        },

        /*合法的MixIO行为种类*/
        this.actionTags = {

            SEND_MQTT_MESSAGE: "21", //发送MQTT消息

            BUTTON_SWITCH: "121", //切换开关状态

            SLIDER_SEND: "221", //改变滑杆数值

            KEYBOARD_SEND: "321", //通过键盘发送消息

            JOYSTICK_SEND: "421", //通过摇杆发送位置消息

            RGB_PICKER_SEND: "521", //通过RGB色盘发送消息

            BULB_CHANGE: "621", //向指示灯发送消息

            TEXT_SCREEN_CHANGE: "721", //向文本显示屏发送消息

            LINE_CHART_CHANGE: "821", //向折线图表发送消息
            LINE_CHART_CLEAR: "822", //清空折线图表消息

            BAR_CHART_CHANGE: "921", //向柱状图表发送消息
            BAR_CHART_CLEAR: "922", //清空柱状图表消息

            DATA_TABLE_CHANGE: "1021", //向数据表格发送消息
            DATA_TABLE_CLEAR: "1022", //清空数据表格消息

            DASHBOARD_CHANGE: "1121", //向仪表盘发送消息

            DATA_MAP_CHANGE: "1221", //向数据地图发送消息
            DATA_MAP_CLEAR: "1222", //清空数据地图数据

            WEATHER_SYNC: "1321", //更新气象仪数据
            WEATHER_SEND: "1322" //发送气象仪数据

        },

        this.setInterval = function(triggerFunction, intervalTime) {
            this.cycles_to_be_removed.push(setInterval(
                function() {
                    try {
                        triggerFunction()
                    } catch (e) {
                        console.log(e.message)
                        that.errorMessage = e.toString()
                        that.stop_project()
                    }
                }, intervalTime))
        },

        this.setTimeout = function(triggerFunction, waitTime) {
            this.timers_to_be_removed.push(setTimeout(
                function() {
                    try {
                        triggerFunction()
                    } catch (e) {
                        that.errorMessage = e.toString()
                        that.stop_project()
                    }
                }, waitTime))
        },

        this.onMessage = function(trigger) {
            var toBeRemoved = function(topic, message) {
                var splitTopic = topic.split('/').pop()
                try {
                    trigger(splitTopic, message)
                } catch (e) {
                    that.errorMessage = e.toString()
                    that.stop_project()
                }
            }
            this.client.on("message", toBeRemoved)
            this.listeners_to_be_removed.push(toBeRemoved)
        },

        this.isValidType = function(typeTag) {
            for (target in this.typeTags) {
                if (typeTag === this.typeTags[target])
                    return true;
            }
            return false;
        },

        /*获取组件实例*/
        this.getInstance = function(name, type) {
            if (!this.isValidType(type))
                throw new MixIOLogicError(1)
            var instance = this.$("[" + "user-title='" + name + "']")
            if (instance.length != 1)
                throw new MixIOLogicError(2)
            if (instance.attr("user-type") != this.oldTags[type - 1])
                throw new MixIOLogicError(3)
            instance.toString = function() {
                return this.zhcnTags[type - 1] + ":" + name
            }
            if (type === this.typeTags.BUTTON) {
                //获取开关状态
                instance.isOn = function() {
                    return instance.attr('user-content') == '1'
                }
            } else if (type === this.typeTags.SLIDER) {
                //获取滑杆数值
                instance.getValue = function() {
                    return parseFloat(instance.attr('user-content').split(',')[3])
                }
            } else if (type === this.typeTags.JOYSTICK) {
                //获取摇杆横坐标
                instance.getX = function() {
                        return parseInt(instance.attr('user-content').split(',')[0])
                    }
                    //获取摇杆纵坐标
                instance.getY = function() {
                    return parseInt(instance.attr('user-content').split(',')[1])
                }
            } else if (type === this.typeTags.KEYBOARD) {
                instance.getText = function() {
                    return instance.attr('user-content')
                }
            } else if (type === this.typeTags.RGB_PICKER) {
                //获取RGB色盘当前颜色
                instance.getColor = function() {
                    var tmp = instance.attr('user-content').split(',')
                    for (i in tmp) {
                        tmp[i] = parseInt(tmp[i])
                    }
                    return tmp
                }
            } else if (type === this.typeTags.BULB) {
                //获取指示灯当前状态
                instance.getStatus = function() {
                    return instance.attr('user-content')
                }
            } else if (type === this.typeTags.TEXT_SCREEN) {
                //获取文本显示屏的当前显示
                instance.getText = function() {
                    return instance.attr('user-content')
                }
            } else if (type === this.typeTags.LINE_CHART) {
                var allJSON = []
                if (instance.attr('user-content').length > 2) {
                    var allMsg = JSON.parse(stringendecoder.decodeHtml(instance.attr('user-content').slice(2)))
                    var prevX = allMsg.prevX.data
                    var series = allMsg.series
                    for (i in prevX) {
                        let oneJSON = {}
                        let j = i
                        oneJSON.time = prevX[j]
                        for (sery in series)
                            oneJSON[series[sery].name] = series[sery]['data'][j]
                        allJSON.push(oneJSON)
                    }
                }
                //获取折线图表的全部历史消息
                instance.getAllMessages = function(index) {
                        return allJSON
                    }
                    //获取折线图表的至多前num条消息
                instance.getLatestMessages = function(num) {
                        return allJSON.slice(0 - num)
                    }
                    //获取折线图表的最新一条消息
                instance.getLatestMessage = function(index) {
                    return allJSON.slice(-1)
                }
            } else if (type === this.typeTags.BAR_CHART) {
                var allMsg = instance.attr('user-content').slice(2).split(',')
                var sepMsgs = {
                    keys: [],
                    values: []
                }
                var count = allMsg.length / 2
                for (msg in allMsg) {
                    if (msg < count)
                        sepMsgs.keys.push(allMsg[msg])
                    else
                        sepMsgs.values.push(allMsg[msg])
                }
                //获取柱状图表的当前数据
                instance.getData = function() {
                    return sepMsgs
                }
            } else if (type === this.typeTags.DATA_TABLE) {
                var allMsg = instance.attr('user-content').split(',')
                var sepMsgs = {}
                var colCount = parseInt(allMsg[0])
                for (var i = 0; i < colCount; i = i + 1) {
                    sepMsgs[allMsg[i + 1]] = []
                }
                for (var i = 1 + colCount; i < allMsg.length; i = i + 1) {
                    sepMsgs[allMsg[(i - 1 - colCount) % colCount + 1]].push(allMsg[i])
                }
                //获取数据表格的全部数据
                instance.getData = function() {
                    return sepMsgs
                }
            } else if (type === this.typeTags.DASHBOARD) {
                //获取仪表盘的当前值
                instance.getValue = function() {
                    return instance.attr('user-content').split(',')[2]
                }
            } else if (type === this.typeTags.WEATHER) {
                var sepMsgs = {
                        district: instance.attr('user-content').split(',')[1],
                        weather_type: instance.attr('user-content').split(',')[2],
                        temperature: instance.attr('user-content').split(',')[3],
                        humidity: instance.attr('user-content').split(',')[4],
                        wind_dir: instance.attr('user-content').split(',')[5],
                        wind_class: instance.attr('user-content').split(',')[6]
                    }
                    //获取气象仪的信息
                instance.getData = function(type) {
                    return sepMsgs[type]
                }
            }
            return instance
        },
        this.getClientid = function() {
            return this.client.options.clientId
        },
        this.getLong = function() {
            return curlong
        },
        this.getLati = function() {
            return curlati
        },
        this.makedom = function() {
            var grid = that.$("#grid")
            var client = that.client
            var units_array = that.$(dom)
            var $ = that.$
            var isMixly = false
            var MixIO = that
            var add_block = function(width, height, contents, attrs) {
                var itemdiv = $("<div/>")
                itemdiv.attr("class", "item")
                contentdiv = $("<div/>")
                contentdiv.attr("class", "item-content")
                for (content in contents)
                    contentdiv.append(contents[content])
                for (attr in attrs)
                    itemdiv.attr(attrs[attr][0], attrs[attr][1])
                itemdiv.append(contentdiv)
                grid.append(itemdiv[0])
                return itemdiv
            }
            var toolkits = {
                'input_button': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var button = $("<label class='switch' style='margin-bottom:0'></label>")
                    var button2 = $("<a class='pushButton'/>")
                    var checkbox = $("<input type='checkbox'>")
                    if (user_content == 1)
                        checkbox.prop('checked', true)
                    else
                        checkbox.prop('checked', false)
                    var checkDiv = $("<div class='slider round'></div>")
                    button.append(checkbox)
                    button.append(checkDiv)
                    contents.push(button)
                    contents.push(button2)
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (title.parent().parent().attr('user-content') != 2 && topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                if (message1 == 0) {
                                    checkbox.prop('checked', false)
                                    title.parent().parent().attr('user-content', 0)
                                } else if (message1 == 1) {
                                    checkbox.prop('checked', true)
                                    title.parent().parent().attr('user-content', 1)
                                }
                                itemdiv.trigger(MixIO.eventTags.BUTTON_CHANGED, [Uint8ArrayToString(message1)])
                            }
                    })
                    attrs = [
                        ['user-type', 'input_button'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(1, 1, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.BUTTON_SWITCH, function(event, status) {
                        checkbox.prop('checked', !!status)
                        MixIO.publish(topic.text(), (!!status) ? 1 : 0)
                    })
                },
                'input_slider': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var sliderDiv = $("<div style='width:100%;display:flex;flex-direction:row;justify-content:center'/>")
                    var slider = $("<input type='range' min='0' max='10' step='1' value='0'></input>")
                    sliderDiv.append(slider)
                    contents.push(sliderDiv)
                    attrs = [
                        ['user-type', 'input_slider'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(3, 1, contents, attrs)
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                if (!isNaN(parseFloat(message1))) {
                                    var val = parseFloat(message1)
                                    if (val >= slider.attr('min') && val <= slider.attr('max')) {
                                        slider.val(val)
                                        title.parent().parent().attr('user-content', slider.attr('min') + "," + slider.attr('max') + "," + slider.attr('step') + "," + slider.val())
                                    }
                                }
                                itemdiv.trigger(MixIO.eventTags.SLIDER_CHANGED, [parseFloat(message1)])
                            }
                    })
                    itemdiv.bind(MixIO.actionTags.SLIDER_SEND, function(event, val) {
                        if (val >= slider.attr('min') && val <= slider.attr('max')) {
                            slider.val(val)
                        }
                        MixIO.publish(topic.text(), val)
                    })
                    var vals = user_content.split(',')
                    slider.attr('min', vals[0])
                    slider.attr('max', vals[1])
                    slider.attr('step', vals[2])
                    slider.val(vals[3])
                },
                'input_keyboard': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    attrs = [
                        ['user-type', 'input_keyboard'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var keyDiv = $("<div style='width:100%;display:flex;flex-direction:row;justify-content:center;align-items:center'/>")
                    var messDiv = $("<input class='form-control' style='width:70%;min-width:0px'/>")
                    messDiv.val(stringendecoder.decodeHtml(user_content))
                    keyDiv.append(messDiv)
                    contents.push(keyDiv)
                    var itemdiv = add_block(3, 1, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.KEYBOARD_SEND, function(event, message) {
                        messDiv.val(message)
                        MixIO.publish(topic.text(), messDiv.val())
                    })
                },
                'input_controller': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var controllerDiv = $("<div style='width:70%;height:70%;'/>")
                    contents.push(controllerDiv)
                    attrs = [
                        ['user-type', 'input_controller'],
                        ['user-title', user_title],
                        ['user-content', "0,0"],
                        ['user-topic', user_topic]
                    ]
                    var itemdiv = add_block(2, 2, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.JOYSTICK_SEND, function(event, x, y) {
                        MixIO.publish(topic.text(), x + "," + y)
                    })
                },
                'input_rgb': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div style='margin-top:5px;margin-bottom:5px;font-size:0.75rem'/>")
                    var Rtopic = $("<span class='index-topic' style='margin:0;color:#858796;;margin-right:10px'>" + user_topic.split('/')[0] + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#e74a3b;margin-right:3px'></i>"))
                    topicDiv.append(Rtopic)
                    var Gtopic = $("<span class='index-topic' style='margin:0;color:#858796;;margin-right:10px'>" + user_topic.split('/')[1] + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#1cc88a;margin-right:3px'></i>"))
                    topicDiv.append(Gtopic)
                    var Btopic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic.split('/')[2] + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#4e73df;margin-right:3px'></i>"))
                    topicDiv.append(Btopic)
                    var RGBDiv = $("<div style='color:black;margin-right:10px;display:flex;flex-direction:column;align-items:center'/>")
                    var RDiv = $("<div style='display:flex;flex-direction:row;align-items:center;justify-content:center;margin-top:5px;margin-bottom:5px'/>")
                    var GDiv = $("<div style='display:flex;flex-direction:row;align-items:center;justify-content:center;margin-top:5px;margin-bottom:5px'/>")
                    var BDiv = $("<div style='display:flex;flex-direction:row;align-items:center;justify-content:center;margin-top:5px;margin-bottom:5px'/>")
                    var RInput = $("<input class='form-control' style='width:45px;padding:3px;min-width:0px' readonly>")
                    var GInput = $("<input class='form-control' style='width:45px;padding:3px;min-width:0px' readonly>")
                    var BInput = $("<input class='form-control' style='width:45px;padding:3px;min-width:0px' readonly>")
                    RInput.val(user_content.split(',')[0])
                    GInput.val(user_content.split(',')[1])
                    BInput.val(user_content.split(',')[2])
                    RDiv.append(RInput)
                    GDiv.append(GInput)
                    BDiv.append(BInput)
                    RGBDiv.append(RDiv)
                    RGBDiv.append(GDiv)
                    RGBDiv.append(BDiv)
                    contents.push(RGBDiv)
                    attrs = [
                        ['user-type', 'input_rgb'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(3, 3, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.RGB_PICKER_SEND, function(event, r, g, b) {
                        MixIO.publish(Rtopic.text(), r)
                        MixIO.publish(Gtopic.text(), g)
                        MixIO.publish(Btopic.text(), b)
                    })
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == Rtopic.text()) {
                                if (!isNaN(parseInt(message1))) {
                                    var val = parseInt(message1)
                                    if (val >= 0 && val <= 255) {
                                        RInput.val(val)
                                        title.parent().parent().attr('user-content', RInput.val() + "," + GInput.val() + "," + BInput.val())
                                        itemdiv.trigger(MixIO.eventTags.RGB_PICKER_CHANGED, [val, -1, -1])
                                    }
                                }
                            }
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == Gtopic.text()) {
                                if (!isNaN(parseInt(message1))) {
                                    var val = parseInt(message1)
                                    if (val >= 0 && val <= 255) {
                                        GInput.val(val)
                                        title.parent().parent().attr('user-content', RInput.val() + "," + GInput.val() + "," + BInput.val())
                                        itemdiv.trigger(MixIO.eventTags.RGB_PICKER_CHANGED, [-1, val, -1])
                                    }
                                }
                            }
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == Btopic.text()) {
                                if (!isNaN(parseInt(message1))) {
                                    var val = parseInt(message1)
                                    if (val >= 0 && val <= 255) {
                                        BInput.val(val)
                                        title.parent().parent().attr('user-content', RInput.val() + "," + GInput.val() + "," + BInput.val())
                                        itemdiv.trigger(MixIO.eventTags.RGB_PICKER_CHANGED, [-1, -1, val])
                                    }
                                }
                            }
                    })
                },
                'output_bulb': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    attrs = [
                        ['user-type', 'output_bulb'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(1, 1, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.BULB_CHANGE, function(event, status) {
                        MixIO.publish(topic.text(), status)
                    })
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                if (message1 == 0) {
                                    title.parent().parent().attr('user-content', 0)
                                } else if (message1 == 1) {
                                    title.parent().parent().attr('user-content', 1)
                                } else if (message1 == 2) {
                                    title.parent().parent().attr('user-content', 2)
                                } else if (message1 == 3) {
                                    title.parent().parent().attr('user-content', 3)
                                }
                                itemdiv.trigger(MixIO.eventTags.BULB_CHANGED, [Uint8ArrayToString(message1)])
                            }
                    })
                },
                'output_text': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var textDiv = $("<div/>")
                    textDiv.text(stringendecoder.decodeHtml(user_content))
                    textDiv.attr('class', 'mid_screen')
                    contents.push(textDiv)
                    attrs = [
                        ['user-type', 'output_text'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(2, 2, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.TEXT_SCREEN_CHANGE, function(event, message) {
                        MixIO.publish(topic.text(), message)
                    })
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                textDiv.text(message1)
                                title.parent().parent().attr('user-content', stringendecoder.encodeHtml(String(message1)))
                                itemdiv.trigger(MixIO.eventTags.TEXT_SCREEN_CHANGED, [String(message1)])
                            }
                    })
                },
                'output_chart': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var titleDiv = $("<div style='display:flex;flex-direction:row;justify-content:center;align-items:center;margin-top:10px'/>")
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    titleDiv.append(title)
                    contents.push(titleDiv)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='color:#858796;margin:0;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if ((topic1.split("/")[(isMixly ? 3 : 2)] == topic.text())) {
                                var label = (new Date().getHours() + ":" + (new Date().getMinutes() < 10 ? "0" : "") + new Date().getMinutes() + ":" + (new Date().getSeconds() < 10 ? "0" : "") + new Date().getSeconds())
                                var data = String(message1)
                                itemdiv.trigger(MixIO.eventTags.LINE_CHART_CHANGED, [label, data])
                            }
                    })
                    attrs = [
                        ['user-type', 'output_chart'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(3, 3, contents, attrs)
                    itemdiv.addClass("moveDiv")
                    itemdiv.bind(MixIO.actionTags.LINE_CHART_CHANGE, function(event, value) {
                        MixIO.publish(topic.text(), value)
                    })
                    itemdiv.bind(MixIO.actionTags.LINE_CHART_CLEAR, function() {

                    })

                },
                'output_bar': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var titleDiv = $("<div style='display:flex;flex-direction:row;justify-content:center;align-items:center;margin-top:10px'/>")
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    titleDiv.append(title)
                    contents.push(titleDiv)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if ((topic1.split("/")[(isMixly ? 3 : 2)] == topic.text())) {
                                var data = String(message1)
                                itemdiv.trigger(MixIO.eventTags.BAR_CHART_CHANGED, [data])
                            }
                    })
                    attrs = [
                        ['user-type', 'output_bar'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(3, 3, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.BAR_CHART_CHANGE, function(event, message) {
                        MixIO.publish(topic.text(), message)
                    })
                    itemdiv.bind(MixIO.actionTags.BAR_CHART_CLEAR, function() {

                    })
                },
                'output_dashboard': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    attrs = [
                        ['user-type', 'output_dashboard'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(2, 2, contents, attrs)
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                if (!isNaN(parseFloat(message1))) {
                                    itemdiv.trigger(MixIO.eventTags.DASHBOARD_CHANGED, [parseFloat(message1)])
                                }
                            }
                    })
                    itemdiv.bind(MixIO.actionTags.DASHBOARD_CHANGE, function(event, value) {
                        MixIO.publish(topic.text(), value)
                    })
                },
                'output_map': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var mapDiv = $("<div style='width:calc(100% - 20px);height:calc(100% - 60px)'/>")
                    contents.push(mapDiv)
                    attrs = [
                        ['user-type', 'output_map'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(2, 2, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.DATA_MAP_CHANGE, function(event, message) {
                        MixIO.publish(topic.text(), JSON.stringify(message))
                    })
                    itemdiv.bind(MixIO.actionTags.DATA_MAP_CLEAR, function() {

                    })
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                if (isJSON(String(message1)) && JSON.parse(String(message1)).long && JSON.parse(String(message1)).lat && JSON.parse(String(message1)).clientid) {
                                    var jsonMessage = JSON.parse(String(message1))
                                    itemdiv.trigger(MixIO.eventTags.DATA_MAP_CHANGED, [jsonMessage.clientid, jsonMessage.long, jsonMessage.lat, jsonMessage.message])
                                }
                            }
                    })
                },
                'input_weather': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    var buttonDiv = $("<div style='margin-top:10px'/>")
                    var district = ''
                    var weather_type = ''
                    var temperature = ''
                    var humidity = ''
                    var wind_dir = ''
                    var wind_class = ''
                    contents.push(buttonDiv)
                    attrs = [
                        ['user-type', 'input_weather'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(2, 2, contents, attrs)
                    var sync_weather = function() {
                        var dsc_code = title.parent().parent().attr('user-content').split(',')[0].split('w')[0]
                        if (globalWeather[dsc_code] && globalWeather[dsc_code].time && (new Date().getTime() - globalWeather[dsc_code].time) < 600000) {
                            var result = globalWeather[dsc_code].data
                            var resJSON = JSON.parse(result)
                            weather_type = resJSON.result.now.text
                            temperature = resJSON.result.now.temp
                            humidity = resJSON.result.now.rh
                            wind_dir = resJSON.result.now.wind_dir
                            wind_class = resJSON.result.now.wind_class
                            district = resJSON.result.location.name
                            title.parent().parent().attr('user-content', [title.parent().parent().attr('user-content').split(',')[0], district, weather_type, temperature, humidity, wind_dir, wind_class].join(','))
                            itemdiv.trigger(MixIO.eventTags.WEATHER_SYNCED, [district, weather_type, temperature, humidity, wind_dir, wind_class])
                        } else {
                            if(configs["BAIDU_MAP_SERVER_AK"])
                            {
                                http.get('http://api.map.baidu.com/weather/v1/?district_id=' + dsc_code + '&data_type=now&ak=' + configs["BAIDU_MAP_SERVER_AK"], function(req2, res2) {
                                    var html = ''
                                    req2.on('data', function(data) {
                                        html += data;
                                    });
                                    req2.on('end', function() {
                                        globalWeather[dsc_code] = {
                                            time: new Date().getTime(),
                                            data: html
                                        }
                                        var result = html
                                        var resJSON = JSON.parse(result)
                                        if (resJSON.result && resJSON.result.now) {
                                            weather_type = resJSON.result.now.text
                                            temperature = resJSON.result.now.temp
                                            humidity = resJSON.result.now.rh
                                            wind_dir = resJSON.result.now.wind_dir
                                            wind_class = resJSON.result.now.wind_class
                                            district = resJSON.result.location.name
                                            title.parent().parent().attr('user-content', [title.parent().parent().attr('user-content').split(',')[0], district, weather_type, temperature, humidity, wind_dir, wind_class].join(','))
                                            itemdiv.trigger(MixIO.eventTags.WEATHER_SYNCED, [district, weather_type, temperature, humidity, wind_dir, wind_class])
                                        }
                                    });
                                })
                            }
                            else if(configs["TENCENT_MAP_KEY"])
                            {
                                http.get('http://apis.map.qq.com/ws/weather/v1/?adcode=' + req.query.dsc_code + '&type=now&key=' + configs["TENCENT_MAP_KEY"], function(req2, res2) {
                                    var html = ''
                                    req2.on('data', function(data) {
                                        html += data;
                                    });
                                    req2.on('end', function() {
                                        try{
                                            var newhtml = JSON.parse(html)['result']['realtime'][0]
                                            html = JSON.stringify({
                                                'status': 0,
                                                'result':{
                                                    'location':{
                                                        'name': newhtml['province'] + newhtml['city'] + newhtml['district']
                                                    },
                                                    'now':{
                                                        'temp': newhtml['infos']['temperature'],
                                                        'text': newhtml['infos']['weather'],
                                                        'rh': newhtml['infos']['humidity'],
                                                        'wind_class': newhtml['infos']['wind_power'],
                                                        'wind_dir': newhtml['infos']['wind_direction']
                                                    }
                                                }
                                            })
                                            globalWeather[req.query.dsc_code] = {
                                                time: new Date().getTime(),
                                                data: html
                                            }
                                            title.parent().parent().attr('user-content', [title.parent().parent().attr('user-content').split(',')[0], newhtml['province'] + newhtml['city'] + newhtml['district'], newhtml['infos']['weather'], newhtml['infos']['temperature'], newhtml['infos']['humidity'], newhtml['infos']['wind_direction'], newhtml['infos']['wind_power']].join(','))
                                            itemdiv.trigger(MixIO.eventTags.WEATHER_SYNCED, [newhtml['province'] + newhtml['city'] + newhtml['district'], newhtml['infos']['weather'], newhtml['infos']['temperature'], newhtml['infos']['humidity'], newhtml['infos']['wind_direction'], newhtml['infos']['wind_power']])
                                        }
                                        catch(e){
                                            res.send('-1')
                                        }
                                    });
                                }).on('error', function(e) {
                                    res.send('-1')
                                })
                            }
                        }
                    }
                    itemdiv.bind(MixIO.actionTags.WEATHER_SYNC, function() {
                        sync_weather()
                    })
                    sync_weather()
                    itemdiv.bind(MixIO.actionTags.WEATHER_SEND, function() {
                        var weather = {
                            'district': district,
                            'weather_type': weather_type,
                            'temperature': temperature,
                            'humidity': humidity,
                            'wind_dir': wind_dir,
                            'wind_class': wind_class
                        }
                        MixIO.publish(topic.text(), JSON.stringify(weather))
                        itemdiv.trigger(MixIO.eventTags.WEATHER_SENT, [district, weather_type, temperature, humidity, wind_dir, wind_class])
                    })
                },
                'trigger': function() {
                    //Removed
                },
                'table': function(user_title, user_topic, user_content, user_style) {
                    var isAlive = true
                    client.on('message', function(topic1, message1) {
                        if (isAlive && that.isRunning)
                            if (topic1.split("/")[(isMixly ? 3 : 2)] == topic.text()) {
                                var message = String(message1).split(',')
                                itemdiv.trigger(MixIO.eventTags.DATA_TABLE_CHANGED, [message])
                            }
                    })
                    var contents = []
                    var title = $("<h4 class='userTitle'>" + user_title + "</h4>")
                    contents.push(title)
                    var topicDiv = $("<div class='topicDiv'/>")
                    var topic = $("<span class='index-topic' style='margin:0;color:#858796;'>" + user_topic + "</span>")
                    topicDiv.append($("<i class='fa fa-podcast' style='color:#858796;margin-right:3px'></i>"))
                    topicDiv.append(topic)
                    attrs = [
                        ['user-type', 'table'],
                        ['user-title', user_title],
                        ['user-topic', user_topic],
                        ['user-content', user_content]
                    ]
                    var itemdiv = add_block(3, 3, contents, attrs)
                    itemdiv.bind(MixIO.actionTags.DATA_TABLE_CHANGE, function(event, message) {
                        MixIO.publish(topic.text(), message)
                    })
                    itemdiv.bind(MixIO.actionTags.DATA_TABLE_CLEAR, function() {

                    })
                },
                'decorate_text': function() {},
                'decorate_pic': function() {},
                'magic': function() {},
                'ocr': function() {}
            }
            for (var ct = 0; ct <= units_array.length - 1; ct = ct + 1) {
                var un = $(units_array[ct])
                toolkits[un.attr('user-type')](un.attr('user-title'), un.attr('user-topic'), un.attr('user-content'), un.attr('style'))
            }
        }()
}

function Uint8ArrayToString(fileData) {
    var dataString = "";
    for (var i = 0; i < fileData.length; i++) {
        dataString += String.fromCharCode(fileData[i]);
    }
    return dataString;
}

function timeStamp2String() {
    var datetime = new Date();
    var year = datetime.getFullYear();
    var month = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
    var date = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
    var hour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours();
    var minute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes();
    var second = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds();
    return year + "-" + month + "-" + date + " " + hour + ":" + minute + ":" + second;
}

function isJSON(str) {
    if (typeof str == 'string') {
        try {
            var obj = JSON.parse(str);
            if (typeof obj == 'object' && obj) {
                return true;
            } else {
                return false;
            }

        } catch (e) {
            return false;
        }
    }
}